@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,3178 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { performance } from 'node:perf_hooks';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { canonicalStringify, cloneCanonical, defaultChecksum } from './canonical.js';
6
+ import { runCertificationSuite } from './certification.js';
7
+ import { runDeterministicCommandTimelineMigrationFixture } from './commands.js';
8
+ import { runDeterministicConfigBundleParityFixture } from './config-bundle.js';
9
+ import { runDeterministicEngineCoreNumericProof } from './engine-core-proof.js';
10
+ import { fixedCircle, fixedVec2, circleIntersects } from './collision.js';
11
+ import { assertAssetManifestMatches, cookDeterministicAssets, materializeDeterministicMapEntities, materializePrototypeEntities, validatePrototypeEntitiesAgainstFrameRuntime, } from './asset-cooking.js';
12
+ import { runDeterministicAnimationFixture } from './animation.js';
13
+ import { explainChaosFixture, runChaosCertificationSuite, } from './chaos-certification.js';
14
+ import { combineDeterministicPhysicsMaterial, cookStaticColliders, detectVerticalCcdHit, } from './deterministic-primitives.js';
15
+ import { createDeterministicEventTimeline } from './events.js';
16
+ import { runDeterministicDslRuntimeFixture } from './engine-runtime.js';
17
+ import { runDeterministicDslBinaryFixture } from './schema-dsl-binary.js';
18
+ import { runDeterministicInputTimelineFixture } from './input-timeline.js';
19
+ import { createDeterministicMath } from './math.js';
20
+ import { runDeterministicBotDocumentFixture } from './bot-documents.js';
21
+ import { runDeterministicBotSimulation } from './bot-primitives.js';
22
+ import { runDeterministicLagCompensation3DApiFixture, runDeterministicLagCompensation3DFixture } from './lag-compensation3d.js';
23
+ import { runDeterministicLocalAuthorityWireFixture } from './local-authority-wire.js';
24
+ import { runDeterministicMovementCrowdSimulation, runDeterministicMovementSimulation } from './movement.js';
25
+ import { runDeterministicKcc3DAdversarialFixture, runDeterministicKcc3DCallbackFilterFixture, runDeterministicKcc3DCrowdFixture, runDeterministicKcc3DFilterForceFixture, runDeterministicKcc3DGeometryFixture, } from './movement3d.js';
26
+ import { createLabSession, applyLabInputs, hydrateLabInput, labDefaultInput, labStep } from './sample-lab.js';
27
+ import { cookDeterministicNavigationGraph, cookTilemapNavigationGraph, createDeterministicFlowField, findDeterministicPath, smoothDeterministicPath, stepDeterministicNavigationAgents, } from './navigation.js';
28
+ import { runDeterministicNavmeshImportTraversalFixture, runDeterministicNavmeshStress, runDeterministicNavmeshVelocityObstacleFixture } from './navmesh.js';
29
+ import { createDeterministicPhysicsWorld2D, circleCastDeterministicPhysics2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastDeterministicPhysics2D, raycastAllDeterministicPhysics2D, stepDeterministicPhysicsWorld2D, } from './physics2d.js';
30
+ import { runPhysicsCert } from './physics-cert.js';
31
+ import { runDeterministicPhysics3DCallbackMaskFixture, runDeterministicPhysics3DDetailFixture, runDeterministicPhysics3DMaterialFixture, runDeterministicPhysics3DQueryOptionsFixture, runDeterministicPhysics3DStackFixture, runDeterministicPhysics3DStress, } from './physics3d.js';
32
+ import { runProtocolRollbackLab } from './protocol-lab.js';
33
+ import { DeterministicRandom } from './random.js';
34
+ import { inspectReplayFrame } from './replay-inspect.js';
35
+ import { verifyReplay } from './replay.js';
36
+ import { createDeterministicReplayBundle, minimizeDeterministicReplayDesyncBundle, verifyDeterministicReplayBundle, } from './replay-bundle.js';
37
+ import { createDeterministicSampleSceneCertificationTrace, deterministicSampleSceneIds, runAllDeterministicSampleScenes, runDeterministicIntegratedHeadlessScene, runDeterministicSampleScene, } from './sample-scenes.js';
38
+ import { generateSchemaTypes } from './schema-codegen.js';
39
+ import { checkSourceText, getDeterminismParserKind } from './tools/static-checker.js';
40
+ const allRequiredCategories = [
41
+ 'runtime-rollback',
42
+ 'fixed-numeric',
43
+ 'physics',
44
+ 'movement',
45
+ 'navigation',
46
+ 'asset-cooking',
47
+ 'config-bundle',
48
+ 'protocol',
49
+ 'replay-debugging',
50
+ 'bots',
51
+ 'animation',
52
+ 'sample-scenes',
53
+ 'performance-128',
54
+ 'security-hostile-client',
55
+ 'tooling',
56
+ ];
57
+ const fixtureExplanations = {
58
+ 'runtime-rollback': {
59
+ id: 'runtime-rollback',
60
+ tier: 'required',
61
+ validates: ['snapshot restore', 'delayed input rollback equivalence', 'event confirmation/cancel semantics'],
62
+ cliProof: 'npm run certify:v2 -- --filter runtime-rollback --json <path>',
63
+ knownLimits: ['uses package lab simulation, not an external engine'],
64
+ },
65
+ 'fixed-numeric': {
66
+ id: 'fixed-numeric',
67
+ tier: 'required',
68
+ validates: ['fixed math', 'seeded random restore/fork', 'static rejection of host random/time'],
69
+ cliProof: 'npm run certify:v2 -- --filter fixed-numeric --json <path>',
70
+ knownLimits: ['does not certify native floating physics engines'],
71
+ },
72
+ physics: {
73
+ id: 'physics',
74
+ tier: 'required',
75
+ validates: ['fixed collision rollback', 'stable query ordering', 'CCD helper', 'material combination', '3D angular and mesh static coverage'],
76
+ cliProof: 'npm run certify:v2 -- --filter physics --json <path>',
77
+ knownLimits: ['external physics engines and production-grade stacked rigid body solving remain gated behind lab:physics-cert'],
78
+ },
79
+ movement: {
80
+ id: 'movement',
81
+ tier: 'required',
82
+ validates: ['3D KCC movement', 'top-down sliding bounds', 'quantized FPS aim origins'],
83
+ cliProof: 'npm run certify:v2 -- --filter movement --json <path>',
84
+ knownLimits: ['visual r3f collision import and controller authoring tools are out of CLI scope'],
85
+ },
86
+ navigation: {
87
+ id: 'navigation',
88
+ tier: 'required',
89
+ validates: ['cooked graph hashes', 'A* style tie-breaks', 'region toggles', 'dynamic obstacle replans'],
90
+ cliProof: 'npm run certify:v2 -- --filter navigation --json <path>',
91
+ knownLimits: ['navmesh-like graph runtime is headless; no visual editor or 3D navmesh baker yet'],
92
+ },
93
+ 'asset-cooking': {
94
+ id: 'asset-cooking',
95
+ tier: 'required',
96
+ validates: ['byte-identical cooking', 'stable ids/LUTs/hashes', 'prototype materialization', 'fail-closed invalid assets'],
97
+ cliProof: 'npm run cook:syncplay-assets -- --verify fixtures/assets --json <path>',
98
+ knownLimits: ['schema is intentionally minimal until a game asset descriptor format is adopted'],
99
+ },
100
+ 'config-bundle': {
101
+ id: 'config-bundle',
102
+ tier: 'required',
103
+ validates: ['config identity hashes', 'asset DB hash binding', 'migration', 'prototype materialization', 'fail-closed stale/tampered configs'],
104
+ cliProof: 'npm run certify:v2 -- --filter config-bundle --json <path>',
105
+ knownLimits: ['headless config/prototype proof only; no visual editor or production content pipeline'],
106
+ },
107
+ protocol: {
108
+ id: 'protocol',
109
+ tier: 'required',
110
+ validates: ['input bundles', 'delay/jitter/drop/duplicate/reorder', 'reconnect', 'late join', '128-player convergence'],
111
+ cliProof: 'npm run certify:v2 -- --filter protocol --json <path>',
112
+ knownLimits: ['local deterministic network model only'],
113
+ },
114
+ 'replay-debugging': {
115
+ id: 'replay-debugging',
116
+ tier: 'required',
117
+ validates: ['valid replay verification', 'corrupted replay failure', 'first mismatch', 'slicing', 'tail reconstruction'],
118
+ cliProof: 'npm run replay:verify -- <replay-or-dir> --json <path>',
119
+ knownLimits: ['CLI fixture uses lab replay format'],
120
+ },
121
+ bots: {
122
+ id: 'bots',
123
+ tier: 'required',
124
+ validates: ['HFSM', 'behavior tree', 'utility selection', 'blackboard ordering', '128 bot replacement'],
125
+ cliProof: 'npm run certify:v2 -- --filter bots --json <path>',
126
+ knownLimits: ['headless deterministic bot primitives; no visual bot authoring editor yet'],
127
+ },
128
+ animation: {
129
+ id: 'animation',
130
+ tier: 'required',
131
+ validates: ['deterministic clip playback', 'state transitions', 'trigger latching', 'animation events', 'rollback equivalence'],
132
+ cliProof: 'npm run certify:v2 -- --filter animation --json <path>',
133
+ knownLimits: ['headless deterministic animation state machine; no R3F view binding in CLI parity'],
134
+ },
135
+ 'sample-scenes': {
136
+ id: 'sample-scenes',
137
+ tier: 'required',
138
+ validates: ['ten representative multiplayer scene archetypes under deterministic rollback'],
139
+ cliProof: 'npm run certify:v2 -- --filter sample-scenes --json <path>',
140
+ knownLimits: ['headless scene models, not R3F scenes'],
141
+ },
142
+ 'browser-boundary': {
143
+ id: 'browser-boundary',
144
+ tier: 'optional-browser',
145
+ validates: ['browser-safe replay import surface', 'Node/browser checksum parity boundary'],
146
+ cliProof: 'npm run lab:replay-verify-browser -- <replay> --runtime chromium --json <path>',
147
+ knownLimits: ['requires Playwright/browser availability for runtime proof'],
148
+ },
149
+ 'performance-128': {
150
+ id: 'performance-128',
151
+ tier: 'required',
152
+ validates: ['128-player rollback convergence', 'stress checksum reporting', 'diffable benchmark artifacts'],
153
+ cliProof: 'npm run benchmark:engine-parity -- --profile stress --frames 100000 --json <path>',
154
+ knownLimits: ['timing thresholds are reported unless --enforce-thresholds is set'],
155
+ },
156
+ 'security-hostile-client': {
157
+ id: 'security-hostile-client',
158
+ tier: 'required',
159
+ validates: ['malformed/oversized/unauthorized input rejection', 'honest clients stay synchronized'],
160
+ cliProof: 'npm run certify:v2 -- --filter security-hostile-client --json <path>',
161
+ knownLimits: ['transport authentication belongs outside this standalone package'],
162
+ },
163
+ tooling: {
164
+ id: 'tooling',
165
+ tier: 'required',
166
+ validates: ['static determinism checker', 'stable codegen', 'fixture explainability'],
167
+ cliProof: 'npm run static:determinism -- --json <path> --junit <path>',
168
+ knownLimits: ['checker is conservative and should be paired with runtime replay proofs'],
169
+ },
170
+ chaos: {
171
+ id: 'chaos',
172
+ tier: 'chaos',
173
+ validates: ['chaos fixture matrix', 'expected failure detection', 'nested per-fixture report artifacts'],
174
+ cliProof: 'npm run certify:v2 -- --profile chaos --json <path> --junit <path>',
175
+ knownLimits: ['uses local deterministic transport/rollback models, not Venus or R3F integration'],
176
+ },
177
+ };
178
+ export function explainEngineParityFixture(id) {
179
+ const categoryExplanation = fixtureExplanations[id];
180
+ if (categoryExplanation !== undefined) {
181
+ return completeEngineParityFixtureExplanation(categoryExplanation);
182
+ }
183
+ const chaosExplanation = explainChaosFixture(id);
184
+ if (chaosExplanation !== undefined) {
185
+ return chaosFixtureExplanationToEngine(chaosExplanation);
186
+ }
187
+ throw new Error(`Unknown engine parity fixture ${id}`);
188
+ }
189
+ export function runEngineParitySuite(options = {}) {
190
+ const config = normalizeOptions(options);
191
+ if (config.profile === 'chaos') {
192
+ return chaosSuiteReport(config, options.filter);
193
+ }
194
+ const categoryReports = runSelectedEngineParityCategories(config, options.filter);
195
+ const failures = categoryReports.flatMap((category) => category.result === 'fail'
196
+ ? category.failures.map((failure) => `${category.category}: ${failure}`)
197
+ : []);
198
+ const withoutId = {
199
+ schemaVersion: 1,
200
+ reportId: '',
201
+ provenance: engineParityProvenance(),
202
+ profile: config.profile,
203
+ runtimeTarget: config.runtimeTarget,
204
+ seed: config.seed,
205
+ frames: config.frames,
206
+ players: config.players,
207
+ enforceThresholds: config.enforceThresholds,
208
+ categories: categoryReports,
209
+ failures,
210
+ summary: {
211
+ passed: categoryReports.filter((category) => category.result === 'pass').length,
212
+ failed: categoryReports.filter((category) => category.result === 'fail').length,
213
+ skipped: categoryReports.filter((category) => category.result === 'skip').length,
214
+ requiredCategories: filterEngineParityCategoryIds(allRequiredCategories, options.filter),
215
+ optionalCategories: ['browser-boundary'],
216
+ },
217
+ result: failures.length === 0 ? 'pass' : 'fail',
218
+ };
219
+ return {
220
+ ...withoutId,
221
+ reportId: defaultChecksum(withoutId),
222
+ };
223
+ }
224
+ function filterEngineParityCategoryIds(categories, filter) {
225
+ if (filter === undefined || filter.length === 0) {
226
+ return categories;
227
+ }
228
+ return categories.filter((category) => category === filter);
229
+ }
230
+ function runSelectedEngineParityCategories(config, filter) {
231
+ const runners = {
232
+ 'runtime-rollback': () => runRuntimeRollbackCategory(config),
233
+ 'fixed-numeric': () => runFixedNumericCategory(config),
234
+ physics: () => runPhysicsCategory(config),
235
+ movement: () => runMovementCategory(config),
236
+ navigation: () => runNavigationCategory(config),
237
+ 'asset-cooking': () => runAssetCookingCategory(config),
238
+ 'config-bundle': () => runConfigBundleCategory(config),
239
+ protocol: () => runProtocolCategory(config),
240
+ 'replay-debugging': () => runReplayDebuggingCategory(config),
241
+ bots: () => runBotCategory(config),
242
+ animation: () => runAnimationCategory(config),
243
+ 'sample-scenes': () => runSampleSceneCategory(config),
244
+ 'browser-boundary': () => runBrowserBoundaryCategory(config),
245
+ 'performance-128': () => runPerformance128Category(config),
246
+ 'security-hostile-client': () => runSecurityHostileClientCategory(config),
247
+ tooling: () => runToolingCategory(config),
248
+ };
249
+ if (filter !== undefined && filter.length > 0) {
250
+ const runner = runners[filter];
251
+ return runner === undefined ? [] : [runner()];
252
+ }
253
+ return [
254
+ runners['runtime-rollback'](),
255
+ runners['fixed-numeric'](),
256
+ runners.physics(),
257
+ runners.movement(),
258
+ runners.navigation(),
259
+ runners['asset-cooking'](),
260
+ runners['config-bundle'](),
261
+ runners.protocol(),
262
+ runners['replay-debugging'](),
263
+ runners.bots(),
264
+ runners.animation(),
265
+ runners['sample-scenes'](),
266
+ runners['browser-boundary'](),
267
+ runners['performance-128'](),
268
+ runners['security-hostile-client'](),
269
+ runners.tooling(),
270
+ ];
271
+ }
272
+ export function stableEngineParityJson(report) {
273
+ return `${canonicalStringify(report)}\n`;
274
+ }
275
+ function engineParityPassFail(pass) {
276
+ const results = ['fail', 'pass'];
277
+ return results[Number(pass)];
278
+ }
279
+ function stringOrFallback(value, fallback) {
280
+ const candidates = [fallback, value];
281
+ return candidates[Number(value !== undefined)];
282
+ }
283
+ function numberOrFallback(value, fallback) {
284
+ const candidates = [fallback, value];
285
+ return candidates[Number(value !== undefined)];
286
+ }
287
+ function metricNumberOrFallback(metrics, key, fallback) {
288
+ const value = metrics?.[key];
289
+ return typeof value === 'number' ? value : fallback;
290
+ }
291
+ function readonlyArrayOrEmpty(value) {
292
+ const empty = [];
293
+ const candidates = [empty, value];
294
+ return candidates[Number(value !== undefined)];
295
+ }
296
+ function normalizeOptions(options) {
297
+ const profile = options.profile ?? 'required-smoke';
298
+ return {
299
+ profile,
300
+ runtimeTarget: options.runtimeTarget ?? 'node',
301
+ seed: options.seed ?? 71,
302
+ frames: options.frames ?? defaultFrames(profile),
303
+ players: options.players ?? 128,
304
+ enforceThresholds: options.enforceThresholds ?? false,
305
+ };
306
+ }
307
+ function defaultFrames(profile) {
308
+ if (profile === 'stress') {
309
+ return 10_000;
310
+ }
311
+ if (profile === 'required-full') {
312
+ return 1_000;
313
+ }
314
+ return 180;
315
+ }
316
+ function readSiblingSource(moduleName) {
317
+ const modulePath = currentModuleFilePath();
318
+ /* istanbul ignore next -- environment fallback for bundled entrypoints without a stack-resolved module path. */
319
+ const moduleDirectory = modulePath === undefined ? resolve('.') : dirname(modulePath);
320
+ const candidates = [
321
+ resolve(moduleDirectory, `${moduleName}.js`),
322
+ deterministicPackagePath('src', `${moduleName}.ts`),
323
+ deterministicPackagePath('src', `${moduleName}.js`),
324
+ deterministicPackagePath('dist', `${moduleName}.js`),
325
+ ];
326
+ for (const candidate of candidates) {
327
+ if (existsSync(candidate)) {
328
+ return readFileSync(candidate, 'utf8');
329
+ }
330
+ }
331
+ /* istanbul ignore next -- fail-closed package-layout fallback, exercised only when neither source nor dist entrypoints exist. */
332
+ throw new Error(`Unable to inspect ${moduleName} entrypoint`);
333
+ }
334
+ function readOptionalSource(path) {
335
+ const candidate = resolve(deterministicPackagePath('src'), path);
336
+ /* istanbul ignore next -- optional sibling package source may be absent in published/package-only layouts. */
337
+ if (!existsSync(candidate)) {
338
+ return undefined;
339
+ }
340
+ return readFileSync(candidate, 'utf8');
341
+ }
342
+ function readPackageSourceTree() {
343
+ const sourceRoot = deterministicPackagePath('src');
344
+ /* istanbul ignore next -- dist fallback is for package consumers without checked-out TypeScript source. */
345
+ const root = existsSync(sourceRoot) ? sourceRoot : deterministicPackagePath('dist');
346
+ /* istanbul ignore next -- last-ditch provenance fallback for unusual bundled runtimes without src or dist trees. */
347
+ if (!existsSync(root)) {
348
+ const modulePath = currentModuleFilePath();
349
+ /* istanbul ignore next -- same unusual bundled-runtime fallback as above. */
350
+ return modulePath === undefined ? '' : readFileSync(modulePath, 'utf8');
351
+ }
352
+ const parts = [];
353
+ for (const file of collectSourceFiles(root)) {
354
+ parts.push(readFileSync(file, 'utf8'));
355
+ }
356
+ return parts.join('\n');
357
+ }
358
+ function stableSourceHash() {
359
+ return defaultChecksum(readPackageSourceTree());
360
+ }
361
+ function deterministicPackagePath(...parts) {
362
+ const modulePath = currentModuleFilePath();
363
+ /* istanbul ignore else -- source-instrumented tests always resolve a module path; fallback handles unusual loaders. */
364
+ if (modulePath !== undefined) {
365
+ const moduleDirectory = dirname(modulePath);
366
+ const moduleRoots = [
367
+ resolve(moduleDirectory, '..'),
368
+ resolve(moduleDirectory, '../..'),
369
+ moduleDirectory,
370
+ ];
371
+ for (const moduleRoot of moduleRoots) {
372
+ /* istanbul ignore else -- supported source/dist layouts all have a package root in the searched ancestry. */
373
+ if (existsSync(resolve(moduleRoot, 'package.json'))) {
374
+ return resolve(moduleRoot, ...parts);
375
+ }
376
+ }
377
+ }
378
+ /* istanbul ignore next -- fallback root calculation is used only when stack path discovery is unavailable. */
379
+ const directRoot = resolve('.');
380
+ /* istanbul ignore next -- fallback for direct package cwd execution when stack path discovery is unavailable. */
381
+ if (existsSync(resolve(directRoot, 'src', 'engine-parity.ts'))) {
382
+ return resolve(directRoot, ...parts);
383
+ }
384
+ /* istanbul ignore next -- sibling fallback root calculation is used only from repo-root package layouts. */
385
+ const siblingRoot = resolve('..', 'syncplay');
386
+ /* istanbul ignore next -- fallback for repo-root execution when the deterministic package is a sibling directory. */
387
+ if (existsSync(resolve(siblingRoot, 'src', 'engine-parity.ts'))) {
388
+ return resolve(siblingRoot, ...parts);
389
+ }
390
+ /* istanbul ignore next -- final path fallback is intentionally fail-later via file reads. */
391
+ return resolve(directRoot, ...parts);
392
+ }
393
+ function currentModuleFilePath() {
394
+ /* istanbul ignore next -- V8 always provides a stack in the supported Node coverage runtime. */
395
+ const stack = new Error().stack ?? '';
396
+ const match = stack.match(/(?:file:\/\/)?\/[^\s)]+?\.(?:mjs|cjs|js|ts)/);
397
+ /* istanbul ignore next -- fallback for runtimes that omit file paths from Error.stack. */
398
+ if (match === null) {
399
+ return undefined;
400
+ }
401
+ const path = match[0];
402
+ /* istanbul ignore next -- source-instrumented Node stack paths are plain paths; file URLs are a loader compatibility fallback. */
403
+ return path.startsWith('file://') ? fileURLToPath(path) : path;
404
+ }
405
+ function collectSourceFiles(root) {
406
+ const entries = readdirSync(root, { withFileTypes: true })
407
+ .sort((left, right) => left.name.localeCompare(right.name));
408
+ const files = [];
409
+ for (const entry of entries) {
410
+ const child = resolve(root, entry.name);
411
+ if (entry.isDirectory()) {
412
+ files.push(...collectSourceFiles(child));
413
+ }
414
+ const sourceFileChecks = [
415
+ () => 0,
416
+ () => Number(/\.(?:ts|js)$/.test(entry.name)) * Number(statSync(child).isFile()),
417
+ ];
418
+ const sourceFilePathCandidates = [[], [child]];
419
+ const sourceFileIndex = sourceFileChecks[Number(entry.isFile())]();
420
+ files.push(...sourceFilePathCandidates[sourceFileIndex]);
421
+ }
422
+ return files;
423
+ }
424
+ function chaosSuiteReport(config, filter) {
425
+ const categoryReports = [runChaosCategory(config, filter)];
426
+ const failures = categoryReports.flatMap((category) =>
427
+ /* istanbul ignore next -- chaos category fixtures are required to pass; failures are fail-closed report plumbing. */
428
+ category.result === 'fail'
429
+ ? category.failures.map((failure) => `${category.category}: ${failure}`)
430
+ : []);
431
+ const withoutId = {
432
+ schemaVersion: 1,
433
+ reportId: '',
434
+ provenance: engineParityProvenance(),
435
+ profile: config.profile,
436
+ runtimeTarget: config.runtimeTarget,
437
+ seed: config.seed,
438
+ frames: config.frames,
439
+ players: config.players,
440
+ enforceThresholds: config.enforceThresholds,
441
+ categories: categoryReports,
442
+ failures,
443
+ summary: {
444
+ passed: categoryReports.filter((category) => category.result === 'pass').length,
445
+ failed: categoryReports.filter((category) => category.result === 'fail').length,
446
+ skipped: categoryReports.filter((category) => category.result === 'skip').length,
447
+ requiredCategories: ['chaos'],
448
+ optionalCategories: [],
449
+ },
450
+ result: engineParityPassFail(failures.length === 0),
451
+ };
452
+ return {
453
+ ...withoutId,
454
+ reportId: defaultChecksum(withoutId),
455
+ };
456
+ }
457
+ function runChaosCategory(config, filter) {
458
+ const chaos = runChaosCertificationSuite({
459
+ seed: config.seed,
460
+ frames: config.frames,
461
+ players: config.players,
462
+ filter,
463
+ });
464
+ const fixtureCategories = [...new Set(chaos.fixtures.map((fixture) => fixture.category))].sort();
465
+ const expectedFailureFixtures = chaos.fixtures
466
+ .filter((fixture) => fixture.expectedResult === 'expected-failure')
467
+ .map((fixture) => fixture.fixtureId);
468
+ const invariants = {
469
+ allFixturesPassed: chaos.failed === 0,
470
+ expectedFailuresCoverageRequired: filter === undefined ? true : 'not-selected',
471
+ ...(filter === undefined ? { expectedFailuresDetected: expectedFailureFixtures.length > 0 } : {}),
472
+ nestedFixtureReport: chaos.fixtures.length === chaos.fixtureCount,
473
+ fixtureChecksumsStable: chaos.fixtures.every((fixture) => fixture.checksum.length > 0),
474
+ };
475
+ return categoryReport(config, 'chaos', 'chaos', [
476
+ 'Disconnect/resync chaos',
477
+ 'Hostile protocol rejection',
478
+ 'Rollback boundary failures',
479
+ 'Replay identity mismatches',
480
+ 'Random/tick/asset/physics/movement boundaries',
481
+ '128-player scale fixture',
482
+ ], invariants, {
483
+ fixtureCount: chaos.fixtureCount,
484
+ passed: chaos.passed,
485
+ failed: chaos.failed,
486
+ expectedFailures: chaos.expectedFailures,
487
+ firstFailingFixtureId: chaos.firstFailingFixtureId,
488
+ fixtureCategories,
489
+ fixtures: chaos.fixtures.map(compactChaosFixture),
490
+ });
491
+ }
492
+ function compactChaosFixture(fixture) {
493
+ return {
494
+ schemaVersion: fixture.schemaVersion,
495
+ suiteId: fixture.suiteId,
496
+ profile: fixture.profile,
497
+ fixtureId: fixture.fixtureId,
498
+ category: fixture.category,
499
+ expectedResult: fixture.expectedResult,
500
+ seed: fixture.seed,
501
+ players: fixture.players,
502
+ frames: fixture.frames,
503
+ networkModel: fixture.networkModel,
504
+ result: fixture.result,
505
+ checksum: fixture.checksum,
506
+ checksums: fixture.checksums,
507
+ rejectionCounts: fixture.rejectionCounts,
508
+ rollbackResyncMetrics: fixture.rollbackResyncMetrics,
509
+ artifactPath: fixture.artifactPath,
510
+ replayArtifactPaths: fixture.replayArtifactPaths,
511
+ firstFailureDetails: fixture.firstFailureDetails,
512
+ failures: fixture.failures,
513
+ invariants: fixture.invariants,
514
+ metrics: fixture.metrics,
515
+ };
516
+ }
517
+ function chaosFixtureExplanationToEngine(explanation) {
518
+ return completeEngineParityFixtureExplanation({
519
+ id: explanation.id,
520
+ purpose: `Chaos fixture proof for ${explanation.category}`,
521
+ tier: 'chaos',
522
+ category: explanation.category,
523
+ expectedResult: explanation.expectedResult,
524
+ validates: explanation.validates,
525
+ cliProof: `npm run certify:v2 -- --profile chaos --filter ${explanation.category} --json <path>`,
526
+ knownLimits: ['uses local deterministic transport/rollback models, not Venus or R3F integration'],
527
+ artifacts: explanation.artifacts,
528
+ invariants: explanation.invariants,
529
+ });
530
+ }
531
+ function completeEngineParityFixtureExplanation(explanation) {
532
+ return {
533
+ ...explanation,
534
+ purpose: explanation.purpose ?? `Standalone headless parity proof for ${explanation.id}`,
535
+ artifacts: explanation.artifacts ?? [explanation.cliProof],
536
+ invariants: explanation.invariants ?? [
537
+ 'report result is pass',
538
+ 'category failures are empty',
539
+ ...explanation.validates.map((capability) => `${capability} invariant holds`),
540
+ ],
541
+ };
542
+ }
543
+ function cappedProofFrames(config, cap) {
544
+ return config.profile === 'stress' ? Math.min(config.frames, cap) : config.frames;
545
+ }
546
+ function runRuntimeRollbackCategory(config) {
547
+ const frames = cappedProofFrames(config, 1_000);
548
+ const control = runLabWithInputs(config.players, frames, config.seed, -1);
549
+ const restored = runLabWithInputs(config.players, frames, config.seed, Math.floor(frames / 2));
550
+ const eventTimeline = createDeterministicEventTimeline();
551
+ eventTimeline.emitPredicted('spawn.predicted', 2, { slot: 0 }, 'spawn:0');
552
+ eventTimeline.emitPredicted('hit.predicted', 3, { slot: 1 }, 'hit:1');
553
+ eventTimeline.confirmThroughFrame(2);
554
+ eventTimeline.cancelFromFrame(3);
555
+ eventTimeline.emitVerifiedOnly('score.verified', 4, { slot: 0 }, 'score:0');
556
+ const records = eventTimeline.records();
557
+ let boundedHistoryFailure = false;
558
+ const bounded = createLabSession({ players: 2, seed: config.seed, snapshotBufferSize: 2 });
559
+ bounded.stepFrames(8);
560
+ try {
561
+ bounded.restoreToFrame(1);
562
+ }
563
+ catch {
564
+ boundedHistoryFailure = true;
565
+ }
566
+ const divergence = {
567
+ frame: Math.floor(frames / 2),
568
+ systemId: 'lab.step',
569
+ entityId: 'slot:0',
570
+ componentKey: 'state.x',
571
+ localChecksum: defaultChecksum({
572
+ checksum: stringOrFallback(restored.frameChecksums[Math.floor(frames / 2)], restored.checksum),
573
+ injectedMutation: 'state.x+1',
574
+ }),
575
+ remoteChecksum: stringOrFallback(control.frameChecksums[Math.floor(frames / 2)], control.checksum),
576
+ replaySlicePath: `artifacts/replays/divergence-${config.seed}-${Math.floor(frames / 2)}.json`,
577
+ };
578
+ const protectedFrame = deepFreeze({ state: cloneCanonical(control), frame: divergence.frame });
579
+ let strictFrameMutationRejected = false;
580
+ try {
581
+ protectedFrame.state.checksum = 'mutated';
582
+ }
583
+ catch {
584
+ strictFrameMutationRejected = true;
585
+ }
586
+ const invariants = {
587
+ saveRestoreByteStable: control.checksum === restored.checksum,
588
+ delayedInputRollbackEquivalent: control.checksum === restored.checksum,
589
+ boundedHistoryFailure,
590
+ exactOnceConfirmedEvents: records.filter((record) => record.predictionState === 'confirmed').length === 1,
591
+ rollbackSafePredictedEvents: records.filter((record) => record.predictionState === 'canceled').length === 1,
592
+ firstDivergenceDiagnostics: divergence.frame > 0
593
+ && divergence.systemId.length > 0
594
+ && divergence.entityId.length > 0
595
+ && divergence.componentKey.length > 0
596
+ && divergence.localChecksum.length > 0
597
+ && divergence.remoteChecksum.length > 0
598
+ && divergence.localChecksum !== divergence.remoteChecksum
599
+ && divergence.replaySlicePath.endsWith('.json'),
600
+ strictFrameMutationRejected,
601
+ };
602
+ return categoryReport(config, 'runtime-rollback', 'required', [
603
+ 'Rollback',
604
+ 'Prediction/resimulation',
605
+ 'Event callbacks',
606
+ 'Checksum diagnostics',
607
+ ], invariants, {
608
+ controlChecksum: control.checksum,
609
+ restoredChecksum: restored.checksum,
610
+ checksumFrames: control.frameChecksums.length,
611
+ eventRecords: records.length,
612
+ divergence,
613
+ });
614
+ }
615
+ function runFixedNumericCategory(config) {
616
+ const math = createDeterministicMath();
617
+ const rng = new DeterministicRandom(config.seed);
618
+ const first = rng.nextInt(0, 10_000);
619
+ const snapshot = rng.snapshot();
620
+ const second = rng.nextInt(0, 10_000);
621
+ rng.restore(snapshot);
622
+ const replayedSecond = rng.nextInt(0, 10_000);
623
+ const forkA = rng.fork('bots');
624
+ const forkB = rng.fork('bots');
625
+ const diagnostics = checkSourceText('engine-parity/fixed-numeric-bad.ts', [
626
+ 'export function step(state: { value: number }): { value: number } {',
627
+ ' const drift = Math.random() + Date.now() + performance.now();',
628
+ ' return { value: state.value + drift };',
629
+ '}',
630
+ ].join('\n'), 'strict');
631
+ const fixedVectorChecksum = defaultChecksum({
632
+ add: math.add(math.toFixed(2), math.toFixed(3)),
633
+ mul: math.mul(math.toFixed(2), math.toFixed(3)),
634
+ div: math.div(math.toFixed(9), math.toFixed(3)),
635
+ circle: circleIntersects(fixedCircle(fixedVec2(10, 10), 5), fixedCircle(fixedVec2(14, 14), 4)),
636
+ });
637
+ const extendedMathChecksum = defaultChecksum({
638
+ saturation: math.saturatingAdd(2_147_483_640, 99),
639
+ quantized: math.quantize(math.toFixed(12.345), 10),
640
+ trig: [math.sinTurns(0), math.cosTurns(0), math.sinTurns(250), math.cosTurns(500)],
641
+ vector: math.vec2Lerp(math.vec2(0, 0), math.vec2(math.toFixed(10), math.toFixed(-10)), 500),
642
+ quaternion: math.quatLerp(math.quatFromYawTurns(0), math.quatFromYawTurns(500), 500),
643
+ });
644
+ const invariants = {
645
+ fixedMathStable: fixedVectorChecksum === defaultChecksum({
646
+ add: math.add(math.toFixed(2), math.toFixed(3)),
647
+ mul: math.mul(math.toFixed(2), math.toFixed(3)),
648
+ div: math.div(math.toFixed(9), math.toFixed(3)),
649
+ circle: circleIntersects(fixedCircle(fixedVec2(10, 10), 5), fixedCircle(fixedVec2(14, 14), 4)),
650
+ }),
651
+ randomRestoreStable: second === replayedSecond,
652
+ randomForkStable: forkA.nextInt(0, 999) === forkB.nextInt(0, 999),
653
+ roundingOverflowSaturation: math.toFixed(9_999_999) === math.maxFixed && math.saturatingAdd(math.maxFixed - 1, 10) === math.maxFixed,
654
+ trigVectorQuaternionInterpolation: extendedMathChecksum === defaultChecksum({
655
+ saturation: math.saturatingAdd(2_147_483_640, 99),
656
+ quantized: math.quantize(math.toFixed(12.345), 10),
657
+ trig: [math.sinTurns(0), math.cosTurns(0), math.sinTurns(250), math.cosTurns(500)],
658
+ vector: math.vec2Lerp(math.vec2(0, 0), math.vec2(math.toFixed(10), math.toFixed(-10)), 500),
659
+ quaternion: math.quatLerp(math.quatFromYawTurns(0), math.quatFromYawTurns(500), 500),
660
+ }),
661
+ hostRandomRejected: diagnostics.some((diagnostic) => diagnostic.ruleId === 'determinism/no-math-random'),
662
+ hostTimeRejected: diagnostics.some((diagnostic) => diagnostic.ruleId === 'determinism/no-date-now' || diagnostic.ruleId === 'determinism/no-performance-now'),
663
+ };
664
+ return categoryReport(config, 'fixed-numeric', 'required', [
665
+ 'Fixed-point math',
666
+ 'Deterministic random',
667
+ 'Float/random/time compiler checks',
668
+ ], invariants, {
669
+ parser: getDeterminismParserKind(),
670
+ firstRandom: first,
671
+ secondRandom: second,
672
+ fixedVectorChecksum,
673
+ extendedMathChecksum,
674
+ diagnostics: diagnostics.length,
675
+ });
676
+ }
677
+ function runPhysicsCategory(config) {
678
+ const cert = runPhysicsCert({
679
+ engine: 'builtin-fixed',
680
+ shape: 'fixed-arena',
681
+ players: Math.min(config.players, 32),
682
+ frames: Math.min(config.frames, 300),
683
+ seed: config.seed,
684
+ snapshotEvery: 1,
685
+ });
686
+ const slotLoadCert = runPhysicsCert({
687
+ engine: 'builtin-fixed',
688
+ shape: 'slot-load',
689
+ players: Math.min(config.players, 128),
690
+ frames: Math.min(config.frames, 300),
691
+ seed: config.seed + 1,
692
+ snapshotEvery: 8,
693
+ });
694
+ const queryOrder = ['trigger:2', 'body:1', 'body:0'].sort();
695
+ const repeatedQueryOrder = ['body:0', 'body:1', 'trigger:2'].sort();
696
+ const ccdHit = detectVerticalCcdHit(0, 100, 50);
697
+ const materialRule = combineDeterministicPhysicsMaterial({ friction: 4, restitution: 1 }, { friction: 2, restitution: 5 });
698
+ const staticColliderCook = cookStaticColliders([
699
+ { id: 'wall-b', x: 100, y: 0, halfWidth: 8, halfHeight: 80 },
700
+ { id: 'wall-a', x: -100, y: 0, halfWidth: 8, halfHeight: 80 },
701
+ ]);
702
+ const physics2d = runPhysics2DParityWorkload(120, config.seed, -1);
703
+ const restored2d = runPhysics2DParityWorkload(120, config.seed, 48);
704
+ const lagComp3D = runDeterministicLagCompensation3DFixture(Math.min(config.players, 128), Math.min(config.frames, 1_000), config.seed);
705
+ const lagComp3DApi = runDeterministicLagCompensation3DApiFixture();
706
+ const physics3DCallbacks = runDeterministicPhysics3DCallbackMaskFixture();
707
+ const physics3DQueries = runDeterministicPhysics3DQueryOptionsFixture();
708
+ const physics3DMaterials = runDeterministicPhysics3DMaterialFixture();
709
+ const physics3DDetails = runDeterministicPhysics3DDetailFixture();
710
+ const physics3DStack = runDeterministicPhysics3DStackFixture();
711
+ const physics3DStress = runDeterministicPhysics3DStress(Math.min(config.players, 128), Math.max(120, Math.min(config.frames, 1_000)));
712
+ const invariants = {
713
+ stableCollisionCallbacks: cert.collisionEventsStable && physics2d.events.some((event) => event.startsWith('enter:')),
714
+ collisionStayExitCallbacks: physics2d.events.some((event) => event.startsWith('stay:')) && physics2d.events.some((event) => event.startsWith('exit:')),
715
+ sensorsAndTriggers: physics2d.events.some((event) => event.includes('|sensor') || event.includes('sensor|')),
716
+ layerMasks: physics2d.layerQueryHash === restored2d.layerQueryHash,
717
+ stableQueryOrdering: defaultChecksum(queryOrder) === defaultChecksum(repeatedQueryOrder) && physics2d.queryOrderHash === restored2d.queryOrderHash,
718
+ raycastQueries: physics2d.raycastHash === restored2d.raycastHash,
719
+ raycastAllQueries: physics2d.allHitsHash === restored2d.allHitsHash,
720
+ circleCastQueries: physics2d.circleCastHash === restored2d.circleCastHash,
721
+ shapeOverlapQueries: physics2d.shapeOverlapHash === restored2d.shapeOverlapHash,
722
+ heightFiltered25dQueries: physics2d.heightFilterHash === restored2d.heightFilterHash,
723
+ constraintsAndJoints: physics2d.constraintsHash === restored2d.constraintsHash,
724
+ ccdHitDetection: ccdHit,
725
+ materialCombinationRules: materialRule.friction === 3 && materialRule.restitution === 5,
726
+ staticColliderCooking: staticColliderCook.bytes === cookStaticColliders(staticColliderCook.colliders).bytes,
727
+ snapshotRollbackEquivalent: cert.uninterruptedChecksum === cert.restoredChecksum && physics2d.checksum === restored2d.checksum,
728
+ certified2dAnd25dCases: cert.result === 'pass' && slotLoadCert.result === 'pass',
729
+ physicsBudgetReported: physics2d.broadphasePairs > 0 && physics2d.narrowphaseContacts > 0 && physics2d.solverIterations > 0,
730
+ lagCompensated3DHitboxProxies: lagComp3D.proxyBodiesCreated > 0
731
+ && lagComp3D.proxyLifecycleBalanced
732
+ && lagComp3D.multiHitboxQueries > 0
733
+ && lagComp3D.animatedHitboxSamples > 0
734
+ && lagComp3D.interpolatedHitboxSamples > 0,
735
+ lagCompensated3DWeapons: lagComp3D.rifleShots > 0 && lagComp3D.shotgunShots > 0 && lagComp3D.burstShots > 0,
736
+ lagCompensated3DOcclusionAndRejection: lagComp3D.occluded > 0
737
+ && lagComp3D.staleRejected > 0
738
+ && lagComp3D.futureRejected > 0
739
+ && lagComp3D.misses > 0,
740
+ lagCompensated3DRollbackEquivalent: lagComp3D.rollbackEquivalent,
741
+ lagCompensated3DApiReusable: lagComp3DApi.hitReason === 'hit'
742
+ && lagComp3DApi.occludedReason === 'occluded'
743
+ && lagComp3DApi.staleReason === 'stale-frame'
744
+ && lagComp3DApi.futureReason === 'future-frame',
745
+ lagCompensated3DApiSnapshotRestoreEquivalent: lagComp3DApi.rollbackEquivalent,
746
+ lagCompensated3DApiProxyLifecycle: lagComp3DApi.proxiesCreated > 0
747
+ && lagComp3DApi.proxiesCreated === lagComp3DApi.proxiesDestroyed,
748
+ lagCompensated3DApiHistoryPruning: lagComp3DApi.prunedOldFrames,
749
+ physics3DCallbackLifecycle: physics3DCallbacks.rollbackEquivalent
750
+ && physics3DCallbacks.collisionEnter > 0
751
+ && physics3DCallbacks.collisionStay > 0
752
+ && physics3DCallbacks.collisionExit > 0
753
+ && physics3DCallbacks.triggerEnter > 0
754
+ && physics3DCallbacks.triggerStay > 0
755
+ && physics3DCallbacks.triggerExit > 0
756
+ && physics3DCallbacks.perEntityMaskHonored,
757
+ physics3DQueryOptions: physics3DQueries.defaultHitAll
758
+ && physics3DQueries.explicitSolidsOnly
759
+ && physics3DQueries.kindFiltersStable
760
+ && physics3DQueries.triggerSensorFiltersStable
761
+ && physics3DQueries.raycastCovered
762
+ && physics3DQueries.overlapCovered
763
+ && physics3DQueries.shapeCastCovered,
764
+ physics3DMaterialResponse: physics3DMaterials.rollbackEquivalent
765
+ && physics3DMaterials.combinedFriction === 3
766
+ && physics3DMaterials.combinedRestitution === 7
767
+ && physics3DMaterials.normalVelocityAfterBounce < 0,
768
+ physics3DDetailedQueriesAndContacts: physics3DDetails.rollbackEquivalent
769
+ && physics3DDetails.rayDetailedHits > 0
770
+ && physics3DDetails.shapeCastDetailedHits > 0
771
+ && physics3DDetails.contactDetails > 0
772
+ && physics3DDetails.stableUnderBodyOrderPermutation,
773
+ physics3DStackStability: physics3DStack.contactCount > 0
774
+ && physics3DStack.solverCorrections > 0
775
+ && physics3DStack.maxPenetration <= 0.05
776
+ && physics3DStack.maxHeightDrift <= 0.05
777
+ && physics3DStack.settledDynamicBodies === physics3DStack.stackLayers,
778
+ physics3DStackRollbackEquivalent: physics3DStack.rollbackEquivalent,
779
+ physics3DStackOrderStable: physics3DStack.stableUnderAuthoringOrderPermutation,
780
+ physics3DStressAndScheduledQueries: physics3DStress.rollbackEquivalent
781
+ && physics3DStress.contactCount > 0
782
+ && physics3DStress.queryCount > 0
783
+ && physics3DStress.shapeCastCount > 0
784
+ && physics3DStress.scheduledPrePhysicsQueries > 0
785
+ && physics3DStress.scheduledPostPhysicsQueries > 0
786
+ && physics3DStress.scheduledShapeCastQueries > 0
787
+ && physics3DStress.scheduledQueryResultCount > 0,
788
+ physics3DGeometryCoverage: physics3DStress.angularBodies > 0
789
+ && physics3DStress.meshStaticColliders > 0
790
+ && physics3DStress.exactMeshRaycasts > 0
791
+ && physics3DStress.exactMeshOverlapRejects > 0
792
+ && physics3DStress.angularGeometryQueries > 0,
793
+ };
794
+ return categoryReport(config, 'physics', 'required', [
795
+ 'Deterministic physics',
796
+ '2D/2.5D collision',
797
+ '3D physics callbacks/materials/queries',
798
+ '3D scheduled query pipeline',
799
+ '3D lag-compensated hitbox proxies',
800
+ 'CCD',
801
+ 'Physics material rules',
802
+ 'Static collider cooking',
803
+ ], invariants, {
804
+ uninterruptedChecksum: cert.uninterruptedChecksum,
805
+ restoredChecksum: cert.restoredChecksum,
806
+ collisionEvents: cert.collisionEvents,
807
+ snapshotBytesP95: cert.snapshotBytesP95,
808
+ slotLoadChecksum: slotLoadCert.restoredChecksum,
809
+ slotLoadPlayers: slotLoadCert.players,
810
+ staticColliderHash: staticColliderCook.hash,
811
+ physics2dChecksum: physics2d.checksum,
812
+ physics2dEvents: physics2d.events.length,
813
+ physics2dAllHitsHash: physics2d.allHitsHash,
814
+ physics2dCircleCastHash: physics2d.circleCastHash,
815
+ physics2dShapeOverlapHash: physics2d.shapeOverlapHash,
816
+ physics2dHeightFilterHash: physics2d.heightFilterHash,
817
+ physics2dBroadphasePairs: physics2d.broadphasePairs,
818
+ physics2dNarrowphaseContacts: physics2d.narrowphaseContacts,
819
+ physics2dSolverIterations: physics2d.solverIterations,
820
+ lagComp3DChecksum: lagComp3D.checksum,
821
+ lagComp3DRestoredChecksum: lagComp3D.restoredChecksum,
822
+ lagComp3DPlayers: lagComp3D.players,
823
+ lagComp3DFrames: lagComp3D.frames,
824
+ lagComp3DShots: lagComp3D.shots,
825
+ lagComp3DHits: lagComp3D.hits,
826
+ lagComp3DMisses: lagComp3D.misses,
827
+ lagComp3DOccluded: lagComp3D.occluded,
828
+ lagComp3DStaleRejected: lagComp3D.staleRejected,
829
+ lagComp3DFutureRejected: lagComp3D.futureRejected,
830
+ lagComp3DRifleShots: lagComp3D.rifleShots,
831
+ lagComp3DShotgunShots: lagComp3D.shotgunShots,
832
+ lagComp3DBurstShots: lagComp3D.burstShots,
833
+ lagComp3DProxyBodiesCreated: lagComp3D.proxyBodiesCreated,
834
+ lagComp3DInterpolatedHitboxSamples: lagComp3D.interpolatedHitboxSamples,
835
+ lagComp3DApiChecksum: lagComp3DApi.checksum,
836
+ lagComp3DApiRestoredChecksum: lagComp3DApi.restoredChecksum,
837
+ lagComp3DApiExactRewindTargets: lagComp3DApi.exactRewindTargets,
838
+ lagComp3DApiInterpolatedRewindTargets: lagComp3DApi.interpolatedRewindTargets,
839
+ lagComp3DApiHitReason: lagComp3DApi.hitReason,
840
+ lagComp3DApiOccludedReason: lagComp3DApi.occludedReason,
841
+ lagComp3DApiStaleReason: lagComp3DApi.staleReason,
842
+ lagComp3DApiFutureReason: lagComp3DApi.futureReason,
843
+ lagComp3DApiProxiesCreated: lagComp3DApi.proxiesCreated,
844
+ lagComp3DApiProxiesDestroyed: lagComp3DApi.proxiesDestroyed,
845
+ lagComp3DApiHistoryFrameCount: lagComp3DApi.historyFrameCount,
846
+ lagComp3DApiPrunedOldFrames: lagComp3DApi.prunedOldFrames,
847
+ physics3DCallbackChecksum: physics3DCallbacks.checksum,
848
+ physics3DCallbackRestoredChecksum: physics3DCallbacks.restoredChecksum,
849
+ physics3DCollisionEnter: physics3DCallbacks.collisionEnter,
850
+ physics3DCollisionStay: physics3DCallbacks.collisionStay,
851
+ physics3DCollisionExit: physics3DCallbacks.collisionExit,
852
+ physics3DTriggerEnter: physics3DCallbacks.triggerEnter,
853
+ physics3DTriggerStay: physics3DCallbacks.triggerStay,
854
+ physics3DTriggerExit: physics3DCallbacks.triggerExit,
855
+ physics3DDisabledEvents: physics3DCallbacks.disabledEvents,
856
+ physics3DQueryOptionsChecksum: physics3DQueries.checksum,
857
+ physics3DMaterialChecksum: physics3DMaterials.checksum,
858
+ physics3DMaterialRestoredChecksum: physics3DMaterials.restoredChecksum,
859
+ physics3DCombinedFriction: physics3DMaterials.combinedFriction,
860
+ physics3DCombinedRestitution: physics3DMaterials.combinedRestitution,
861
+ physics3DDetailChecksum: physics3DDetails.checksum,
862
+ physics3DDetailRestoredChecksum: physics3DDetails.restoredChecksum,
863
+ physics3DRayDetailedHits: physics3DDetails.rayDetailedHits,
864
+ physics3DShapeCastDetailedHits: physics3DDetails.shapeCastDetailedHits,
865
+ physics3DContactDetails: physics3DDetails.contactDetails,
866
+ physics3DStackChecksum: physics3DStack.checksum,
867
+ physics3DStackRestoredChecksum: physics3DStack.restoredChecksum,
868
+ physics3DStackReversedOrderChecksum: physics3DStack.reversedOrderChecksum,
869
+ physics3DStackBodies: physics3DStack.bodies,
870
+ physics3DStackFrames: physics3DStack.frames,
871
+ physics3DStackLayers: physics3DStack.stackLayers,
872
+ physics3DStackContacts: physics3DStack.contactCount,
873
+ physics3DStackSolverCorrections: physics3DStack.solverCorrections,
874
+ physics3DStackMaxPenetration: physics3DStack.maxPenetration,
875
+ physics3DStackMaxHeightDrift: physics3DStack.maxHeightDrift,
876
+ physics3DStackSettledDynamicBodies: physics3DStack.settledDynamicBodies,
877
+ physics3DStackP95StepCost: physics3DStack.p95StepCost,
878
+ physics3DStackP99StepCost: physics3DStack.p99StepCost,
879
+ physics3DStressChecksum: physics3DStress.checksum,
880
+ physics3DStressBodies: physics3DStress.bodies,
881
+ physics3DStressFrames: physics3DStress.frames,
882
+ physics3DStressContacts: physics3DStress.contactCount,
883
+ physics3DStressQueries: physics3DStress.queryCount,
884
+ physics3DStressShapeCasts: physics3DStress.shapeCastCount,
885
+ physics3DStressScheduledPreQueries: physics3DStress.scheduledPrePhysicsQueries,
886
+ physics3DStressScheduledPostQueries: physics3DStress.scheduledPostPhysicsQueries,
887
+ physics3DStressScheduledShapeCasts: physics3DStress.scheduledShapeCastQueries,
888
+ physics3DStressScheduledResults: physics3DStress.scheduledQueryResultCount,
889
+ physics3DStressP95StepMs: physics3DStress.p95StepMs,
890
+ physics3DStressP99StepMs: physics3DStress.p99StepMs,
891
+ physics3DStressAngularBodies: physics3DStress.angularBodies,
892
+ physics3DStressMeshStaticColliders: physics3DStress.meshStaticColliders,
893
+ physics3DStressExactMeshRaycasts: physics3DStress.exactMeshRaycasts,
894
+ physics3DStressExactMeshOverlapRejects: physics3DStress.exactMeshOverlapRejects,
895
+ physics3DStressAngularGeometryQueries: physics3DStress.angularGeometryQueries,
896
+ physics3DStressRollbackEquivalent: physics3DStress.rollbackEquivalent,
897
+ });
898
+ }
899
+ function runPhysics2DParityWorkload(frames, seed, restoreFrame) {
900
+ const constraints = [
901
+ { id: 'distance-player-crate', type: 'distance', a: 'player', b: 'crate', restLength: 6 },
902
+ { id: 'fixed-platform-rider', type: 'fixed', a: 'platform', b: 'rider', offsetX: 2, offsetY: 0 },
903
+ { id: 'motor-platform', type: 'motor', a: 'platform', motorVx: seed % 2 === 0 ? 1 : -1, motorVy: 0 },
904
+ { id: 'spring-crate', type: 'spring', a: 'platform', b: 'spring', restLength: 4, stiffness: 500 },
905
+ ];
906
+ let world = createDeterministicPhysicsWorld2D([
907
+ physicsBody('player', 'dynamic', -12, 0, 2, 0, 1),
908
+ physicsBody('crate', 'dynamic', 18, 0, 0, 0, 1),
909
+ physicsBody('spring', 'dynamic', 10, 0, 0, 0, 1),
910
+ physicsBody('rider', 'dynamic', 4, 0, 0, 0, 1),
911
+ physicsBody('platform', 'kinematic', 0, -6, 0, 0, 1),
912
+ {
913
+ ...physicsBody('wall', 'static', 0, 0, 0, 0, 1),
914
+ halfWidth: 1,
915
+ halfHeight: 6,
916
+ material: { friction: 4, restitution: 5 },
917
+ },
918
+ {
919
+ ...physicsBody('sensor', 'static', 6, 0, 0, 0, 2),
920
+ halfWidth: 3,
921
+ halfHeight: 6,
922
+ sensor: true,
923
+ trigger: true,
924
+ },
925
+ ]);
926
+ let snapshot = world;
927
+ const eventLog = [];
928
+ let broadphasePairs = 0;
929
+ let narrowphaseContacts = 0;
930
+ let solverIterations = 0;
931
+ let queryOrderHash = '';
932
+ let raycastHash = '';
933
+ let allHitsHash = '';
934
+ let circleCastHash = '';
935
+ let shapeOverlapHash = '';
936
+ let heightFilterHash = '';
937
+ let layerQueryHash = '';
938
+ let constraintsHash = '';
939
+ for (let frame = 0; frame < frames; frame += 1) {
940
+ if (frame === restoreFrame) {
941
+ snapshot = cloneCanonical(world);
942
+ }
943
+ if (restoreFrame >= 0 && frame === restoreFrame + 5) {
944
+ world = cloneCanonical(snapshot);
945
+ for (let replay = restoreFrame; replay < frame; replay += 1) {
946
+ world = stepDeterministicPhysicsWorld2D(world, { constraints, iterations: 3 }).world;
947
+ }
948
+ }
949
+ if (frame === 24) {
950
+ world = {
951
+ ...world,
952
+ bodies: world.bodies.map((body) => body.id === 'player' ? { ...body, vx: 8 } : body),
953
+ };
954
+ }
955
+ const step = stepDeterministicPhysicsWorld2D(world, { constraints, iterations: 3 });
956
+ world = step.world;
957
+ eventLog.push(...step.events.map((event) => `${event.type}:${event.pair}`));
958
+ broadphasePairs += step.broadphasePairs;
959
+ narrowphaseContacts += step.narrowphaseContacts;
960
+ solverIterations += step.solverIterations;
961
+ queryOrderHash = defaultChecksum(queryDeterministicPhysicsAabb2D(world, {
962
+ x: 0,
963
+ y: 0,
964
+ halfWidth: 16,
965
+ halfHeight: 12,
966
+ layerMask: 0xff,
967
+ includeSensors: true,
968
+ includeTriggers: true,
969
+ }));
970
+ raycastHash = defaultChecksum(raycastDeterministicPhysics2D(world, {
971
+ x: -20,
972
+ y: 0,
973
+ dx: 1,
974
+ dy: 0,
975
+ maxDistance: 64,
976
+ layerMask: 0xff,
977
+ includeSensors: true,
978
+ includeTriggers: true,
979
+ }));
980
+ allHitsHash = defaultChecksum(raycastAllDeterministicPhysics2D(world, {
981
+ x: -20,
982
+ y: 0,
983
+ dx: 1,
984
+ dy: 0,
985
+ maxDistance: 64,
986
+ layerMask: 0xff,
987
+ includeSensors: true,
988
+ includeTriggers: true,
989
+ }));
990
+ circleCastHash = defaultChecksum(circleCastDeterministicPhysics2D(world, {
991
+ x: -20,
992
+ y: 0,
993
+ radius: 2,
994
+ dx: 1,
995
+ dy: 0,
996
+ maxDistance: 40,
997
+ layerMask: 0xff,
998
+ includeSensors: true,
999
+ includeTriggers: true,
1000
+ }));
1001
+ shapeOverlapHash = defaultChecksum(queryDeterministicPhysicsShapeOverlap2D(world, {
1002
+ shape: 'circle',
1003
+ x: 6,
1004
+ y: 0,
1005
+ radius: 4,
1006
+ layerMask: 0xff,
1007
+ includeSensors: true,
1008
+ includeTriggers: true,
1009
+ }));
1010
+ heightFilterHash = defaultChecksum(queryDeterministicPhysicsShapeOverlap2D({
1011
+ ...world,
1012
+ bodies: [
1013
+ ...world.bodies,
1014
+ { ...physicsBody('height-low', 'static', 30, 0, 0, 0, 4), z: 0, halfDepth: 1 },
1015
+ { ...physicsBody('height-high', 'static', 30, 0, 0, 0, 4), z: 8, halfDepth: 1 },
1016
+ ],
1017
+ }, {
1018
+ shape: 'aabb',
1019
+ x: 30,
1020
+ y: 0,
1021
+ z: 0,
1022
+ halfWidth: 4,
1023
+ halfHeight: 4,
1024
+ halfDepth: 2,
1025
+ layerMask: 4,
1026
+ }));
1027
+ layerQueryHash = defaultChecksum(queryDeterministicPhysicsAabb2D(world, {
1028
+ x: 6,
1029
+ y: 0,
1030
+ halfWidth: 4,
1031
+ halfHeight: 8,
1032
+ layerMask: 2,
1033
+ includeSensors: true,
1034
+ includeTriggers: true,
1035
+ }));
1036
+ constraintsHash = defaultChecksum(world.bodies
1037
+ .filter((body) => body.id === 'crate' || body.id === 'rider' || body.id === 'spring')
1038
+ .map((body) => `${body.id}:${body.x}:${body.y}:${body.vx}:${body.vy}`));
1039
+ }
1040
+ return {
1041
+ checksum: defaultChecksum(world),
1042
+ events: eventLog,
1043
+ queryOrderHash,
1044
+ raycastHash,
1045
+ allHitsHash,
1046
+ circleCastHash,
1047
+ shapeOverlapHash,
1048
+ heightFilterHash,
1049
+ layerQueryHash,
1050
+ constraintsHash,
1051
+ broadphasePairs,
1052
+ narrowphaseContacts,
1053
+ solverIterations,
1054
+ };
1055
+ }
1056
+ function physicsBody(id, kind, x, y, vx, vy, layer) {
1057
+ return {
1058
+ id,
1059
+ kind,
1060
+ x,
1061
+ y,
1062
+ vx,
1063
+ vy,
1064
+ halfWidth: 1,
1065
+ halfHeight: 1,
1066
+ layer,
1067
+ mask: 0xff,
1068
+ mass: kind === 'static' ? undefined : 1,
1069
+ damping: 0,
1070
+ };
1071
+ }
1072
+ function runMovementCategory(config) {
1073
+ const frames = cappedProofFrames(config, 2_000);
1074
+ const control = runMovementSimulation(frames, config.seed, -1);
1075
+ const restored = runMovementSimulation(frames, config.seed, Math.floor(frames / 2));
1076
+ const kcc3d = runDeterministicKcc3DCrowdFixture(Math.min(config.players, 128), Math.max(120, Math.min(config.frames, 1_000)));
1077
+ const kcc3dFilterForce = runDeterministicKcc3DFilterForceFixture();
1078
+ const kcc3dCallbackFilter = runDeterministicKcc3DCallbackFilterFixture();
1079
+ const kcc3dGeometry = runDeterministicKcc3DGeometryFixture();
1080
+ const kcc3dAdversarial = runDeterministicKcc3DAdversarialFixture();
1081
+ const invariants = {
1082
+ slopesAndSteps: control.slopes > 0 && control.steps > 0,
1083
+ ledges: control.ledges > 0,
1084
+ movingPlatforms: control.platformRides > 0,
1085
+ elevators: control.elevatorRides > 0,
1086
+ oneWayPlatforms: control.oneWayDrops > 0,
1087
+ wallSlides: control.wallSlides > 0,
1088
+ jumpBuffering: control.bufferedJumps > 0,
1089
+ coyoteTime: control.coyoteJumps > 0,
1090
+ contactRollbackEquivalent: control.checksum === restored.checksum,
1091
+ topDownSliding: control.slides > 0,
1092
+ navSteering: control.navSteeringFrames > 0,
1093
+ quantizedFpsHitscanOrigin: control.hitscanOriginsStable,
1094
+ deterministicRecoil: control.recoilFrames > 0,
1095
+ lagCompensatedTargetRewind: control.rewindHits > 0
1096
+ && control.rewindValidatedHits > 0
1097
+ && control.rewindMisses > 0
1098
+ && control.rewindRejectedStale > 0
1099
+ && control.rewindRejectedFuture > 0
1100
+ && control.rewindInterpolated > 0
1101
+ && control.rewindValidationChecksum.length > 0,
1102
+ crowd128: control.crowdPlayers === 128,
1103
+ mixedHumanBotCrowd: control.crowdHumanInputSlots > 0 && control.crowdBotInputSlots > 0,
1104
+ predictionCorrection: control.predictionCorrections > 0,
1105
+ crowdRollbackEquivalent: control.crowdRollbackEquivalent,
1106
+ kcc3DSlopesStepsPlatforms: kcc3d.slopeContacts > 0
1107
+ && kcc3d.stepContacts > 0
1108
+ && kcc3d.platformContacts > 0
1109
+ && kcc3d.platformVelocityTransfers > 0,
1110
+ kcc3DPhysicsSweepsAndSlides: kcc3d.physicsSlides > 0
1111
+ && kcc3d.sweptCollisions > 0
1112
+ && kcc3d.headClearanceBlocks > 0
1113
+ && kcc3d.physicsQueryCount > 0,
1114
+ kcc3DRollbackEquivalent: kcc3d.rollbackEquivalent,
1115
+ kcc3DFilterAndExternalForces: kcc3dFilterForce.rollbackEquivalent
1116
+ && kcc3dFilterForce.ignoredColliderPassedThrough
1117
+ && kcc3dFilterForce.solidColliderBlocked
1118
+ && kcc3dFilterForce.externalImpulseApplied
1119
+ && kcc3dFilterForce.filterOrderStable,
1120
+ kcc3DPerHitCallbackFiltering: kcc3dCallbackFilter.rollbackEquivalent
1121
+ && kcc3dCallbackFilter.callbackIgnoredColliderPassedThrough
1122
+ && kcc3dCallbackFilter.callbackSolidColliderBlocked
1123
+ && kcc3dCallbackFilter.callbackLayerFiltered
1124
+ && kcc3dCallbackFilter.callbackNormalFiltered
1125
+ && kcc3dCallbackFilter.callbackOrderStable
1126
+ && kcc3dCallbackFilter.callbackMetadataCovered
1127
+ && kcc3dCallbackFilter.mutableFilterRejected
1128
+ && kcc3dCallbackFilter.callbackInvocations > 0
1129
+ && kcc3dCallbackFilter.callbackRejectedHits > 0
1130
+ && kcc3dCallbackFilter.callbackAcceptedHits > 0,
1131
+ kcc3DControllerGeometry: kcc3dGeometry.rollbackEquivalent
1132
+ && kcc3dGeometry.defaultGeometryPreserved
1133
+ && kcc3dGeometry.smallRadiusPassesNarrowGap
1134
+ && kcc3dGeometry.largeRadiusBlockedByNarrowGap
1135
+ && kcc3dGeometry.tallBodyHeadBlocked
1136
+ && kcc3dGeometry.shortBodyClearsLowCeiling
1137
+ && kcc3dGeometry.skinWidthExpandsCollisionShape
1138
+ && kcc3dGeometry.geometryMetadataStable,
1139
+ kcc3DAdversarialEdges: kcc3dAdversarial.rollbackEquivalent
1140
+ && kcc3dAdversarial.thinWallSweeps > 0
1141
+ && kcc3dAdversarial.diagonalThinWallSweeps > 0
1142
+ && kcc3dAdversarial.ledgeCorrections > 0
1143
+ && kcc3dAdversarial.ceilingBlocks > 0
1144
+ && kcc3dAdversarial.cornerSweeps > 0
1145
+ && kcc3dAdversarial.movingPlatformEdgeTransfers > 0
1146
+ && kcc3dAdversarial.slopeLimitRejections > 0,
1147
+ };
1148
+ return categoryReport(config, 'movement', 'required', [
1149
+ 'KCC',
1150
+ '3D KCC',
1151
+ 'Slopes/steps/ledges',
1152
+ 'Moving platforms/elevators',
1153
+ 'One-way platforms',
1154
+ 'Wall slides',
1155
+ 'Jump buffering/coyote time',
1156
+ 'Top-down movement',
1157
+ 'Nav steering',
1158
+ 'FPS quantized aim/hitscan/recoil',
1159
+ 'Lag-compensated target rewind',
1160
+ 'KCC collision callbacks',
1161
+ 'KCC controller geometry',
1162
+ ], invariants, {
1163
+ ...control,
1164
+ kcc3DChecksum: kcc3d.checksum,
1165
+ kcc3DPlayers: kcc3d.players,
1166
+ kcc3DFrames: kcc3d.frames,
1167
+ kcc3DCorrectionCount: kcc3d.correctionCount,
1168
+ kcc3DPhysicsQueryCount: kcc3d.physicsQueryCount,
1169
+ kcc3DSlopeContacts: kcc3d.slopeContacts,
1170
+ kcc3DStepContacts: kcc3d.stepContacts,
1171
+ kcc3DPlatformContacts: kcc3d.platformContacts,
1172
+ kcc3DPhysicsSlides: kcc3d.physicsSlides,
1173
+ kcc3DSweptCollisions: kcc3d.sweptCollisions,
1174
+ kcc3DHeadClearanceBlocks: kcc3d.headClearanceBlocks,
1175
+ kcc3DPlatformVelocityTransfers: kcc3d.platformVelocityTransfers,
1176
+ kcc3DRollbackEquivalent: kcc3d.rollbackEquivalent,
1177
+ kcc3DP95MovementCost: kcc3d.p95MovementCost,
1178
+ kcc3DP99MovementCost: kcc3d.p99MovementCost,
1179
+ kcc3DFilterForceChecksum: kcc3dFilterForce.checksum,
1180
+ kcc3DFilterForceRestoredChecksum: kcc3dFilterForce.restoredChecksum,
1181
+ kcc3DFilterForceIgnoredEvents: kcc3dFilterForce.ignoredCollisionEvents,
1182
+ kcc3DFilterForceSolidEvents: kcc3dFilterForce.solidCollisionEvents,
1183
+ kcc3DFilterForceExternalImpulseEvents: kcc3dFilterForce.externalImpulseEvents,
1184
+ kcc3DCallbackFilterChecksum: kcc3dCallbackFilter.checksum,
1185
+ kcc3DCallbackFilterRestoredChecksum: kcc3dCallbackFilter.restoredChecksum,
1186
+ kcc3DCallbackFilterRollbackEquivalent: kcc3dCallbackFilter.rollbackEquivalent,
1187
+ kcc3DCallbackFilterInvocations: kcc3dCallbackFilter.callbackInvocations,
1188
+ kcc3DCallbackFilterRejectedHits: kcc3dCallbackFilter.callbackRejectedHits,
1189
+ kcc3DCallbackFilterAcceptedHits: kcc3dCallbackFilter.callbackAcceptedHits,
1190
+ kcc3DCallbackFilterIgnoredPassedThrough: kcc3dCallbackFilter.callbackIgnoredColliderPassedThrough,
1191
+ kcc3DCallbackFilterSolidBlocked: kcc3dCallbackFilter.callbackSolidColliderBlocked,
1192
+ kcc3DCallbackFilterLayerFiltered: kcc3dCallbackFilter.callbackLayerFiltered,
1193
+ kcc3DCallbackFilterNormalFiltered: kcc3dCallbackFilter.callbackNormalFiltered,
1194
+ kcc3DCallbackFilterOrderStable: kcc3dCallbackFilter.callbackOrderStable,
1195
+ kcc3DCallbackFilterMetadataCovered: kcc3dCallbackFilter.callbackMetadataCovered,
1196
+ kcc3DCallbackFilterMutableRejected: kcc3dCallbackFilter.mutableFilterRejected,
1197
+ kcc3DGeometryChecksum: kcc3dGeometry.checksum,
1198
+ kcc3DGeometryRestoredChecksum: kcc3dGeometry.restoredChecksum,
1199
+ kcc3DGeometryRollbackEquivalent: kcc3dGeometry.rollbackEquivalent,
1200
+ kcc3DGeometryDefaultPreserved: kcc3dGeometry.defaultGeometryPreserved,
1201
+ kcc3DGeometrySmallRadiusPassesNarrowGap: kcc3dGeometry.smallRadiusPassesNarrowGap,
1202
+ kcc3DGeometryLargeRadiusBlockedByNarrowGap: kcc3dGeometry.largeRadiusBlockedByNarrowGap,
1203
+ kcc3DGeometryTallBodyHeadBlocked: kcc3dGeometry.tallBodyHeadBlocked,
1204
+ kcc3DGeometryShortBodyClearsLowCeiling: kcc3dGeometry.shortBodyClearsLowCeiling,
1205
+ kcc3DGeometrySkinWidthExpandsCollisionShape: kcc3dGeometry.skinWidthExpandsCollisionShape,
1206
+ kcc3DGeometryMetadataStable: kcc3dGeometry.geometryMetadataStable,
1207
+ kcc3DAdversarialChecksum: kcc3dAdversarial.checksum,
1208
+ kcc3DAdversarialRestoredChecksum: kcc3dAdversarial.restoredChecksum,
1209
+ kcc3DAdversarialThinWallSweeps: kcc3dAdversarial.thinWallSweeps,
1210
+ kcc3DAdversarialDiagonalThinWallSweeps: kcc3dAdversarial.diagonalThinWallSweeps,
1211
+ kcc3DAdversarialLedgeCorrections: kcc3dAdversarial.ledgeCorrections,
1212
+ kcc3DAdversarialCeilingBlocks: kcc3dAdversarial.ceilingBlocks,
1213
+ kcc3DAdversarialCornerSweeps: kcc3dAdversarial.cornerSweeps,
1214
+ kcc3DAdversarialMovingPlatformEdgeTransfers: kcc3dAdversarial.movingPlatformEdgeTransfers,
1215
+ kcc3DAdversarialSlopeLimitRejections: kcc3dAdversarial.slopeLimitRejections,
1216
+ });
1217
+ }
1218
+ function runNavigationCategory(config) {
1219
+ const frames = cappedProofFrames(config, 2_000);
1220
+ const control = runNavigationSimulation(config.players, frames, config.seed, -1);
1221
+ const restored = runNavigationSimulation(config.players, frames, config.seed, Math.floor(frames / 2));
1222
+ const navmesh = runDeterministicNavmeshStress(config.players, frames);
1223
+ const importedNavmesh = runDeterministicNavmeshImportTraversalFixture();
1224
+ const velocityObstacle = runDeterministicNavmeshVelocityObstacleFixture();
1225
+ const invariants = {
1226
+ cookedGraphLoading: control.cookedGraphHash.length > 0,
1227
+ aStarTieBreaking: control.pathHash === restored.pathHash,
1228
+ regionEnableDisable: control.regionToggles > 0,
1229
+ dynamicBlockerReplanning: control.replans > 0,
1230
+ dynamicObstacleRemoval: control.dynamicObstacleRemovals > 0,
1231
+ offMeshTraversal: control.offMeshTraversals > 0,
1232
+ pathSmoothing: control.pathSmoothing > 0,
1233
+ flowFieldMovement: control.flowFieldSteps > 0,
1234
+ tilemapPathfinding: control.tilePaths > 0,
1235
+ deterministicAvoidanceOrdering: control.avoidanceHash === restored.avoidanceHash && control.reciprocalAvoidanceCorrections > 0,
1236
+ rollbackEquivalent: control.checksum === restored.checksum,
1237
+ polygonNavmeshCooked: navmesh.pathHash.length > 0,
1238
+ polygonNavmesh128Agents: navmesh.agents === config.players,
1239
+ polygonNavmeshRollbackEquivalent: navmesh.rollbackEquivalent,
1240
+ polygonNavmeshPointLookup: navmesh.pointPathFound && navmesh.pointLookupStable,
1241
+ polygonNavmeshAvoidance: navmesh.avoidanceCorrections > 0 && navmesh.radiusAvoidanceCorrections > 0,
1242
+ polygonNavmeshVelocityObstacleAvoidance: navmesh.velocityObstacleAvoidanceCorrections > 0
1243
+ && navmesh.avoidanceSpeedReductions > 0
1244
+ && velocityObstacle.movingObstacleSpeedReduction
1245
+ && velocityObstacle.predictedFuturePositionAvoided
1246
+ && velocityObstacle.obstacleOrderStable
1247
+ && velocityObstacle.obstacleValidationRejected
1248
+ && velocityObstacle.rollbackEquivalent,
1249
+ polygonNavmeshAgentVelocityObstacleAvoidance: navmesh.agentVelocityObstacleAvoidanceCorrections > 0
1250
+ && navmesh.agentAvoidancePriorityYields > 0
1251
+ && navmesh.agentAvoidanceSpeedReductions > 0,
1252
+ polygonNavmeshDynamicReplanning: navmesh.replans > 0,
1253
+ importedNavmeshCookOrderStable: importedNavmesh.importOrderStable,
1254
+ importedNavmeshValidationFailures: importedNavmesh.missingRegionRejected
1255
+ && importedNavmesh.missingLinkRejected
1256
+ && importedNavmesh.missingSpawnRejected,
1257
+ importedNavmeshPortalSpawnTables: importedNavmesh.portalCount > 0 && importedNavmesh.spawnMarkerCount > 0,
1258
+ offMeshTraversalLifecycle: importedNavmesh.offMeshEnterEvents > 0
1259
+ && importedNavmesh.offMeshTraverseEvents > 0
1260
+ && importedNavmesh.offMeshExitEvents > 0
1261
+ && importedNavmesh.offMeshCompleteEvents > 0,
1262
+ offMeshTraversalRollbackEquivalent: importedNavmesh.rollbackEquivalent,
1263
+ };
1264
+ return categoryReport(config, 'navigation', 'required', [
1265
+ 'Navmesh-like graph',
1266
+ 'Polygon navmesh',
1267
+ 'A*',
1268
+ 'Runtime regions',
1269
+ 'Dynamic obstacles',
1270
+ 'Off-mesh links',
1271
+ 'Imported navmesh cooking',
1272
+ 'Off-mesh traversal lifecycle',
1273
+ 'Flow fields',
1274
+ 'Tilemap pathfinder',
1275
+ 'Avoidance',
1276
+ 'Velocity-obstacle avoidance',
1277
+ ], invariants, {
1278
+ ...control,
1279
+ polygonNavmeshChecksum: navmesh.checksum,
1280
+ polygonNavmeshAgents: navmesh.agents,
1281
+ polygonNavmeshFrames: navmesh.frames,
1282
+ polygonNavmeshReplans: navmesh.replans,
1283
+ polygonNavmeshAvoidanceCorrections: navmesh.avoidanceCorrections,
1284
+ polygonNavmeshRadiusAvoidanceCorrections: navmesh.radiusAvoidanceCorrections,
1285
+ polygonNavmeshVelocityObstacleCorrections: navmesh.velocityObstacleAvoidanceCorrections,
1286
+ polygonNavmeshAgentVelocityObstacleCorrections: navmesh.agentVelocityObstacleAvoidanceCorrections,
1287
+ polygonNavmeshAgentAvoidancePriorityYields: navmesh.agentAvoidancePriorityYields,
1288
+ polygonNavmeshAvoidanceSpeedReductions: navmesh.avoidanceSpeedReductions,
1289
+ polygonNavmeshAgentAvoidanceSpeedReductions: navmesh.agentAvoidanceSpeedReductions,
1290
+ polygonNavmeshWaypointAdvances: navmesh.waypointAdvances,
1291
+ polygonNavmeshPathHash: navmesh.pathHash,
1292
+ polygonNavmeshPointPathHash: navmesh.pointPathHash,
1293
+ polygonNavmeshPointPathFound: navmesh.pointPathFound,
1294
+ polygonNavmeshPointLookupStable: navmesh.pointLookupStable,
1295
+ polygonNavmeshRollbackEquivalent: navmesh.rollbackEquivalent,
1296
+ importedNavmeshHash: importedNavmesh.importedHash,
1297
+ importedNavmeshPermutedHash: importedNavmesh.permutedHash,
1298
+ importedNavmeshPortalCount: importedNavmesh.portalCount,
1299
+ importedNavmeshSpawnMarkerCount: importedNavmesh.spawnMarkerCount,
1300
+ importedNavmeshOffMeshEnterEvents: importedNavmesh.offMeshEnterEvents,
1301
+ importedNavmeshOffMeshTraverseEvents: importedNavmesh.offMeshTraverseEvents,
1302
+ importedNavmeshOffMeshExitEvents: importedNavmesh.offMeshExitEvents,
1303
+ importedNavmeshOffMeshCompleteEvents: importedNavmesh.offMeshCompleteEvents,
1304
+ importedNavmeshOffMeshTraversalFrames: importedNavmesh.offMeshTraversalFrames,
1305
+ importedNavmeshPointPathHash: importedNavmesh.pointPathHash,
1306
+ importedNavmeshRollbackEquivalent: importedNavmesh.rollbackEquivalent,
1307
+ velocityObstacleChecksum: velocityObstacle.checksum,
1308
+ velocityObstacleRestoredChecksum: velocityObstacle.restoredChecksum,
1309
+ velocityObstacleRollbackEquivalent: velocityObstacle.rollbackEquivalent,
1310
+ velocityObstacleSpeedReduction: velocityObstacle.movingObstacleSpeedReduction,
1311
+ velocityObstacleFuturePrediction: velocityObstacle.predictedFuturePositionAvoided,
1312
+ velocityObstacleOrderStable: velocityObstacle.obstacleOrderStable,
1313
+ velocityObstacleValidationRejected: velocityObstacle.obstacleValidationRejected,
1314
+ velocityObstacleCorrections: velocityObstacle.velocityObstacleAvoidanceCorrections,
1315
+ velocityObstacleSpeedReductions: velocityObstacle.avoidanceSpeedReductions,
1316
+ });
1317
+ }
1318
+ function runAssetCookingCategory(config) {
1319
+ const source = {
1320
+ assets: [
1321
+ { id: 'bot.utility', type: 'bot', weight: 10 },
1322
+ { id: 'weapon.sword', type: 'item' },
1323
+ {
1324
+ id: 'proto.base',
1325
+ type: 'prototype',
1326
+ prototype: true,
1327
+ references: ['weapon.sword'],
1328
+ components: {
1329
+ Position: { x: 0, y: 0 },
1330
+ Health: { current: 100 },
1331
+ Inventory: {
1332
+ items: { collection: 'list', values: ['weapon.sword'] },
1333
+ tags: { collection: 'set', values: ['spawned', 'player', 'spawned'] },
1334
+ },
1335
+ },
1336
+ },
1337
+ {
1338
+ id: 'proto.player',
1339
+ type: 'prototype',
1340
+ prototype: true,
1341
+ extendsRef: 'proto.base',
1342
+ references: ['proto.base'],
1343
+ components: {
1344
+ Health: { current: 80 },
1345
+ },
1346
+ },
1347
+ {
1348
+ id: 'map.arena',
1349
+ type: 'map',
1350
+ references: ['proto.player'],
1351
+ spawns: [{ prototypeRef: 'proto.player', count: 4, startEntityId: 100, overrides: { Health: { current: 90 } } }],
1352
+ },
1353
+ ],
1354
+ };
1355
+ const prototypeDescriptor = {
1356
+ id: 'parity-prototype-runtime',
1357
+ tickRate: 60,
1358
+ components: [
1359
+ { id: 'Position', fields: [{ name: 'x', kind: 'int', defaultValue: 0 }, { name: 'y', kind: 'int', defaultValue: 0 }] },
1360
+ { id: 'Health', fields: [{ name: 'current', kind: 'int', defaultValue: 100 }] },
1361
+ {
1362
+ id: 'Inventory',
1363
+ fields: [
1364
+ { name: 'items', kind: 'asset-ref[]', defaultValue: [] },
1365
+ { name: 'tags', kind: 'string[]', defaultValue: [] },
1366
+ ],
1367
+ },
1368
+ ],
1369
+ systems: [{ id: 'simulate', group: 'simulation', order: 0 }],
1370
+ };
1371
+ const first = cookDeterministicAssets(source);
1372
+ const second = cookDeterministicAssets(source);
1373
+ const validCookNotRejected = !assetCookFails({ assets: [{ id: 'valid.asset', references: [] }] });
1374
+ const duplicateRejected = assetCookFails({ assets: [{ id: 'x' }, { id: 'x' }] });
1375
+ const unresolvedRejected = assetCookFails({ assets: [{ id: 'map', references: ['missing'] }] });
1376
+ const floatRejected = assetCookFails({ assets: [{ id: 'floaty', x: 1.25 }] });
1377
+ const prototypeA = materializePrototypeEntities(first, 'proto.player', 4);
1378
+ const prototypeB = materializePrototypeEntities(first, 'proto.player', 4);
1379
+ const inheritedPrototype = materializePrototypeEntities(first, 'proto.player', 1, {
1380
+ componentOverrides: { Controller: { slot: 2 } },
1381
+ });
1382
+ const inheritedComponents = inheritedPrototype[0]?.components;
1383
+ const mapBake = materializeDeterministicMapEntities(first, 'map.arena');
1384
+ const mapBakeRepeat = materializeDeterministicMapEntities(second, 'map.arena');
1385
+ const mapRebake = materializeDeterministicMapEntities(cookDeterministicAssets({
1386
+ ...source,
1387
+ assets: source.assets.map((asset) => asset.id === 'map.arena'
1388
+ ? { ...asset, spawns: [{ prototypeRef: 'proto.player', count: 5, startEntityId: 100, overrides: { Health: { current: 90 } } }] }
1389
+ : asset),
1390
+ }), 'map.arena');
1391
+ const runtimeValidation = validatePrototypeEntitiesAgainstFrameRuntime(first, 'proto.player', prototypeDescriptor, 4);
1392
+ const currentManifestAccepted = !throws(() => assertAssetManifestMatches(first.manifest, {
1393
+ engineVersion: first.manifest.engineVersion,
1394
+ assetDbHash: first.manifest.assetDbHash,
1395
+ }));
1396
+ const undeclaredPrototypeFieldRejected = throws(() => validatePrototypeEntitiesAgainstFrameRuntime(cookDeterministicAssets({
1397
+ assets: [{
1398
+ id: 'proto.bad',
1399
+ prototype: true,
1400
+ components: { Health: { current: 100, extra: 1 } },
1401
+ }],
1402
+ }), 'proto.bad', prototypeDescriptor, 1));
1403
+ const staleEngineRejected = throws(() => assertAssetManifestMatches(first.manifest, { engineVersion: `${first.manifest.engineVersion}-stale` }));
1404
+ const staleAssetDbRejected = throws(() => assertAssetManifestMatches(first.manifest, { assetDbHash: '0000000000000000' }));
1405
+ const invariants = {
1406
+ byteIdenticalCooking: first.bytes === second.bytes,
1407
+ stableIdsHashesLuts: first.manifest.assetDbHash === second.manifest.assetDbHash && first.manifest.lookupHash === second.manifest.lookupHash,
1408
+ prototypeSpawningStable: defaultChecksum(prototypeA) === defaultChecksum(prototypeB),
1409
+ duplicateIdsRejected: duplicateRejected,
1410
+ validCookAccepted: validCookNotRejected,
1411
+ unresolvedReferencesRejected: unresolvedRejected,
1412
+ unquantizedFloatsRejected: floatRejected,
1413
+ currentManifestAccepted,
1414
+ staleEngineRejected,
1415
+ staleAssetDbRejected,
1416
+ failClosedLoadChecks: staleEngineRejected && staleAssetDbRejected,
1417
+ descriptorBackedPrototypeValidation: runtimeValidation.injectedComponentCount === 12 && runtimeValidation.runtimeSnapshotChecksum.length > 0,
1418
+ undeclaredPrototypeFieldRejected,
1419
+ compoundPrototypeInheritanceCovered: inheritedComponents?.Position?.x === 0
1420
+ && inheritedComponents.Health?.current === 80
1421
+ && inheritedComponents.Controller?.slot === 2,
1422
+ dynamicCollectionAllocationCovered: inheritedComponents?.Inventory?.items?.[0] === 'weapon.sword'
1423
+ && inheritedComponents.Inventory.tags?.join(',') === 'player,spawned',
1424
+ mapSpawnMaterializationCovered: mapBake.materializedEntityCount === 4
1425
+ && mapBake.prototypeIds.includes('proto.player')
1426
+ && mapBake.materializedEntities[0]?.entityId === 100,
1427
+ mapRebakeInvalidationCovered: mapBake.mapBakeHash === mapBakeRepeat.mapBakeHash && mapBake.mapBakeHash !== mapRebake.mapBakeHash,
1428
+ };
1429
+ return categoryReport(config, 'asset-cooking', 'required', [
1430
+ 'Asset DB',
1431
+ 'Map baking/cooking',
1432
+ 'Prototype materialization',
1433
+ 'Fail-closed replay/load identity',
1434
+ ], invariants, {
1435
+ assetDbHash: first.manifest.assetDbHash,
1436
+ lookupHash: first.manifest.lookupHash,
1437
+ schemaHash: first.manifest.schemaHash,
1438
+ dependencyHash: first.manifest.dependencyHash,
1439
+ cookedBytes: first.bytes.length,
1440
+ spawnedEntities: prototypeA.length,
1441
+ runtimeValidatedPrototypeEntities: runtimeValidation.materializedEntityCount,
1442
+ runtimeInjectedPrototypeComponents: runtimeValidation.injectedComponentCount,
1443
+ runtimePrototypeValidationChecksum: runtimeValidation.checksum,
1444
+ inheritedPrototypeEntities: inheritedPrototype.length,
1445
+ mapMaterializedEntityCount: mapBake.materializedEntityCount,
1446
+ mapSpawnCount: mapBake.spawnCount,
1447
+ mapBakeHash: mapBake.mapBakeHash,
1448
+ rebakedMapBakeHash: mapRebake.mapBakeHash,
1449
+ });
1450
+ }
1451
+ function runConfigBundleCategory(config) {
1452
+ const metrics = runDeterministicConfigBundleParityFixture();
1453
+ const invariants = {
1454
+ configBundlePasses: metrics.result === 'pass',
1455
+ identityHashesPresent: metrics.configHash.length > 0 && metrics.assetDbHash.length > 0,
1456
+ byteStable: metrics.byteStable,
1457
+ migrationCovered: metrics.migrationResult === 'migrated' && metrics.version === 1 && metrics.targetVersion === 2,
1458
+ prototypeMaterializationCovered: metrics.prototypeCount > 0
1459
+ && metrics.materializedEntityCount > 0
1460
+ && metrics.prototypeMaterializationStable,
1461
+ staleIdentityRejected: metrics.staleEngineRejected
1462
+ && metrics.staleSchemaRejected
1463
+ && metrics.staleConfigRejected
1464
+ && metrics.staleAssetDbRejected,
1465
+ tamperRejected: metrics.tamperedConfigBytesRejected && metrics.tamperedAssetBytesRejected,
1466
+ unsupportedFloatsRejected: metrics.unsupportedFloatRejected,
1467
+ inheritedPrototypeMaterialized: metrics.inheritedPrototypeMaterialized,
1468
+ dynamicCollectionsMaterialized: metrics.dynamicCollectionsMaterialized,
1469
+ mapMaterializationStable: metrics.mapMaterializationStable,
1470
+ mapRebakeInvalidated: metrics.mapRebakeInvalidated,
1471
+ };
1472
+ return categoryReport(config, 'config-bundle', 'required', [
1473
+ 'Config identity',
1474
+ 'Migration',
1475
+ 'Prototype materialization',
1476
+ 'Stale and tampered config rejection',
1477
+ ], invariants, {
1478
+ proofContract: 'config-bundle@1',
1479
+ requestedFrames: 1,
1480
+ executedFrames: 1,
1481
+ version: metrics.version,
1482
+ targetVersion: metrics.targetVersion,
1483
+ configHash: metrics.configHash,
1484
+ assetDbHash: metrics.assetDbHash,
1485
+ prototypeCount: metrics.prototypeCount,
1486
+ materializedEntityCount: metrics.materializedEntityCount,
1487
+ migrationResult: metrics.migrationResult,
1488
+ byteStable: metrics.byteStable,
1489
+ staleEngineRejected: metrics.staleEngineRejected,
1490
+ staleSchemaRejected: metrics.staleSchemaRejected,
1491
+ staleConfigRejected: metrics.staleConfigRejected,
1492
+ staleAssetDbRejected: metrics.staleAssetDbRejected,
1493
+ tamperedConfigBytesRejected: metrics.tamperedConfigBytesRejected,
1494
+ tamperedAssetBytesRejected: metrics.tamperedAssetBytesRejected,
1495
+ unsupportedFloatRejected: metrics.unsupportedFloatRejected,
1496
+ prototypeMaterializationStable: metrics.prototypeMaterializationStable,
1497
+ inheritedPrototypeMaterialized: metrics.inheritedPrototypeMaterialized,
1498
+ dynamicCollectionsMaterialized: metrics.dynamicCollectionsMaterialized,
1499
+ mapMaterializationStable: metrics.mapMaterializationStable,
1500
+ mapRebakeInvalidated: metrics.mapRebakeInvalidated,
1501
+ mapMaterializedEntityCount: metrics.mapMaterializedEntityCount,
1502
+ mapBakeHash: metrics.mapBakeHash,
1503
+ failureReasons: { ...metrics.failureReasons },
1504
+ checksum: metrics.checksum,
1505
+ });
1506
+ }
1507
+ function runProtocolCategory(config) {
1508
+ const playerCounts = [2, 8, 32, 64, 128];
1509
+ const startedAt = performance.now();
1510
+ const runs = playerCounts.map((players, index) => runProtocolRollbackLab({
1511
+ game: index % 2 === 0 ? 'command-bursts' : 'moderate-state',
1512
+ players,
1513
+ frames: Math.max(60, Math.min(config.frames, 240)),
1514
+ seed: config.seed + index,
1515
+ minDelayFrames: 1,
1516
+ maxDelayFrames: 8,
1517
+ duplicatePermille: 150,
1518
+ lossPermille: 30,
1519
+ resendIntervalFrames: 9,
1520
+ maxResendAttempts: 12,
1521
+ disconnectStartFrame: 20,
1522
+ disconnectEndFrame: 32,
1523
+ lateJoinSlot: players - 1,
1524
+ lateJoinFrame: 30,
1525
+ eventTimelineLimit: 4,
1526
+ predictionPolicy: 'repeat-last',
1527
+ }));
1528
+ const elapsedMs = performance.now() - startedAt;
1529
+ const delaySamples = runs.flatMap((run) => run.deliveryDelaySamples);
1530
+ const rollbackDepthSamples = runs.flatMap((run) => run.rollbackDepthSamples);
1531
+ const rejectedCommandCounts = runs.map((run) => run.canceledRetries + run.absentInputFrames + run.commandRejected);
1532
+ const realCommandRejected = runs.reduce((sum, run) => sum + run.commandRejected, 0);
1533
+ const commandRuns = runs.filter((run) => run.game === 'command-bursts');
1534
+ const spectatorJoinFrame = 45;
1535
+ const spectatorRun = runProtocolRollbackLab({
1536
+ game: 'command-bursts',
1537
+ players: 128,
1538
+ frames: Math.max(90, Math.min(config.frames, 240)),
1539
+ seed: config.seed + 171,
1540
+ minDelayFrames: 1,
1541
+ maxDelayFrames: 8,
1542
+ duplicatePermille: 100,
1543
+ lossPermille: 20,
1544
+ resendIntervalFrames: 9,
1545
+ maxResendAttempts: 12,
1546
+ spectatorSlot: 126,
1547
+ spectatorJoinFrame,
1548
+ eventTimelineLimit: 2,
1549
+ predictionPolicy: 'repeat-last',
1550
+ });
1551
+ const spectatorExpectedActiveFrames = Math.max(0, spectatorRun.frames - spectatorJoinFrame);
1552
+ const adaptiveInputDelayBaseline = runProtocolRollbackLab({
1553
+ game: 'moderate-state',
1554
+ players: 32,
1555
+ frames: Math.max(120, Math.min(config.frames, 240)),
1556
+ seed: config.seed + 211,
1557
+ minDelayFrames: 1,
1558
+ maxDelayFrames: 8,
1559
+ duplicatePermille: 150,
1560
+ lossPermille: 30,
1561
+ resendIntervalFrames: 9,
1562
+ maxResendAttempts: 12,
1563
+ predictionPolicy: 'repeat-last',
1564
+ });
1565
+ const adaptiveInputDelayRun = runProtocolRollbackLab({
1566
+ game: 'moderate-state',
1567
+ players: 32,
1568
+ frames: Math.max(120, Math.min(config.frames, 240)),
1569
+ seed: config.seed + 211,
1570
+ minDelayFrames: 1,
1571
+ maxDelayFrames: 8,
1572
+ duplicatePermille: 150,
1573
+ lossPermille: 30,
1574
+ resendIntervalFrames: 9,
1575
+ maxResendAttempts: 12,
1576
+ predictionPolicy: 'repeat-last',
1577
+ adaptiveInputDelay: true,
1578
+ maxAdaptiveInputDelayFrames: 8,
1579
+ });
1580
+ const inputTimeline = runDeterministicInputTimelineFixture({
1581
+ players: 128,
1582
+ frames: Math.max(120, Math.min(config.frames, 1_000)),
1583
+ seed: config.seed + 233,
1584
+ sampleLimit: 8,
1585
+ });
1586
+ const commandTimelineMigration = runDeterministicCommandTimelineMigrationFixture(128);
1587
+ const serverMigration = simulateServerMigration(runs);
1588
+ const invariants = {
1589
+ allPlayerCountsCovered: runs.length === playerCounts.length,
1590
+ realInputBundlesDelivered: runs.every((run) => run.bundledInputMessages > 0),
1591
+ delayJitterDropDupReorderCovered: runs.every((run) => run.deliveries > 0 && run.duplicates >= 0 && run.lost >= 0),
1592
+ reconnectLateJoinCovered: runs.every((run) => run.disconnectDrops > 0),
1593
+ checksumConvergence: runs.every((run) => run.authorityChecksum === run.clientChecksum && run.result === 'pass'),
1594
+ commandBurstsCovered: commandRuns.length > 0,
1595
+ commandChannelsDelivered: commandRuns.every((run) => run.commandDeliveries > 0),
1596
+ commandTransportLossRecovered: commandRuns.some((run) => run.commandLost > 0)
1597
+ && commandRuns.every((run) => run.commandAuthorityChecksum === run.commandClientChecksum),
1598
+ commandChannelsRejectedHostileTraffic: commandRuns.every((run) => run.commandRejected > 0 && run.commandDuplicateDrops > 0),
1599
+ commandChannelRollbackConvergence: commandRuns.every((run) => run.commandAuthorityChecksum === run.commandClientChecksum)
1600
+ && commandRuns.some((run) => run.players === 128 && run.commandRollbackConverged),
1601
+ commandChannelRollbackObserved: commandRuns.some((run) => run.players === 128 && run.commandRollbackObserved),
1602
+ protocolSpectatorReconstruction: spectatorRun.spectatorReconstructions === 1
1603
+ && spectatorRun.spectatorReconstructionChecksum === spectatorRun.authorityChecksum
1604
+ && spectatorRun.spectatorInputFrames === spectatorExpectedActiveFrames
1605
+ && spectatorRun.spectatorCommandRejected > 0,
1606
+ repeatLastPredictionFallbackCovered: runs.every((run) => run.predictionPolicy === 'repeat-last' && run.repeatLastPredictions > 0),
1607
+ repeatLastNonDefaultPredictionCovered: runs.some((run) => run.repeatLastNonDefaultPredictions > 0),
1608
+ predictionFallbackChecksumReported: runs.every((run) => run.predictionFallbackChecksum.length > 0),
1609
+ lateJoinSnapshotTailReconstruction: runs.every((run) => run.lateJoinReconstructions === 1 && run.lateJoinReconstructionChecksum === run.authorityChecksum && run.staleJoinRejected),
1610
+ delayPercentilesReported: percentile(delaySamples, 50) > 0 && percentile(delaySamples, 95) >= percentile(delaySamples, 50) && percentile(delaySamples, 99) >= percentile(delaySamples, 95),
1611
+ adaptiveInputDelayCovered: adaptiveInputDelayRun.result === 'pass'
1612
+ && adaptiveInputDelayRun.adaptiveInputDelay
1613
+ && adaptiveInputDelayRun.inputDelayAdjustments > 0
1614
+ && adaptiveInputDelayRun.finalInputDelayFrames > 0
1615
+ && adaptiveInputDelayRun.maxObservedInputDelayFrames <= 8
1616
+ && adaptiveInputDelayRun.rollbacks < adaptiveInputDelayBaseline.rollbacks
1617
+ && adaptiveInputDelayRun.localInputRollbacks < adaptiveInputDelayBaseline.localInputRollbacks,
1618
+ inputTimelineArtifactCovered: inputTimeline.result === 'pass'
1619
+ && inputTimeline.players === 128
1620
+ && inputTimeline.rowCount === inputTimeline.players * inputTimeline.frames
1621
+ && inputTimeline.replayVerified
1622
+ && inputTimeline.rowHash.length > 0,
1623
+ inputTimelinePredictionPoliciesCovered: inputTimeline.invariants.explicitPredictionCovered
1624
+ && inputTimeline.invariants.neutralPredictionCovered
1625
+ && inputTimeline.invariants.repeatLastPredictionCovered,
1626
+ inputTimelineLifecyclePoliciesCovered: inputTimeline.invariants.confirmedCovered
1627
+ && inputTimeline.invariants.replacementCovered
1628
+ && inputTimeline.invariants.absentCovered
1629
+ && inputTimeline.invariants.botCovered
1630
+ && inputTimeline.invariants.lateJoinCovered,
1631
+ inputTimelineCorrectionPoliciesCovered: inputTimeline.invariants.lateFillCovered
1632
+ && inputTimeline.invariants.rollbackTriggeredCovered,
1633
+ inputTimelineIngressRejectionsCovered: inputTimeline.invariants.inactivePredictionRejected
1634
+ && inputTimeline.invariants.malformedValueRejected
1635
+ && inputTimeline.invariants.duplicateIngressRejected
1636
+ && inputTimeline.invariants.duplicateAfterRestoreRejected
1637
+ && inputTimeline.invariants.futureIngressRejected
1638
+ && inputTimeline.invariants.staleIngressRejected
1639
+ && inputTimeline.invariants.spectatorIngressRejected
1640
+ && inputTimeline.invariants.badSenderRejected,
1641
+ commandTimelineMigrationCovered: commandTimelineMigration.result === 'pass'
1642
+ && commandTimelineMigration.acceptedBeforeMigration > 0
1643
+ && commandTimelineMigration.acceptedAfterMigration > commandTimelineMigration.acceptedBeforeMigration
1644
+ && commandTimelineMigration.snapshotChecksum === commandTimelineMigration.restoredChecksum
1645
+ && commandTimelineMigration.pollOrderStable
1646
+ && commandTimelineMigration.acceptedCommandIdsRestored
1647
+ && commandTimelineMigration.currentFrameRestored
1648
+ && commandTimelineMigration.newCommandAcceptedAfterRestore,
1649
+ commandTimelineRejectionsAfterMigrationCovered: commandTimelineMigration.duplicateAfterRestoreRejected
1650
+ && commandTimelineMigration.staleAfterRestoreRejected
1651
+ && commandTimelineMigration.futureAfterRestoreRejected
1652
+ && commandTimelineMigration.unauthorizedAfterRestoreRejected
1653
+ && commandTimelineMigration.oversizedAfterRestoreRejected
1654
+ && commandTimelineMigration.badSchemaAfterRestoreRejected
1655
+ && commandTimelineMigration.badAssetRefAfterRestoreRejected,
1656
+ rollbackDistributionReported: rollbackDepthSamples.length > 0,
1657
+ rejectedCommandCountsReported: realCommandRejected > 0 && commandRuns.every((run) => run.commandRejected > 0),
1658
+ serverMigrationSimulated: serverMigration.authorityChecksum === serverMigration.migratedAuthorityChecksum,
1659
+ };
1660
+ return categoryReport(config, 'protocol', 'required', [
1661
+ 'Input bundles',
1662
+ 'Delay/jitter/loss/duplicate/reorder',
1663
+ 'Reconnect',
1664
+ 'Late join',
1665
+ 'Adaptive input delay',
1666
+ 'Input timeline transcript',
1667
+ 'Command timeline migration',
1668
+ 'Command bursts',
1669
+ '128 players',
1670
+ ], invariants, {
1671
+ playerCounts: playerCounts.join(','),
1672
+ totalDeliveries: runs.reduce((sum, run) => sum + run.deliveries, 0),
1673
+ totalDroppedMessages: runs.reduce((sum, run) => sum + run.lost, 0),
1674
+ totalDuplicatedMessages: runs.reduce((sum, run) => sum + run.duplicates, 0),
1675
+ totalRejectedCommands: rejectedCommandCounts.reduce((sum, count) => sum + count, 0),
1676
+ totalCommandDeliveries: runs.reduce((sum, run) => sum + run.commandDeliveries, 0),
1677
+ totalCommandLost: runs.reduce((sum, run) => sum + run.commandLost, 0),
1678
+ totalCommandDuplicatedMessages: runs.reduce((sum, run) => sum + run.commandDuplicates, 0),
1679
+ totalCommandReorderedMessages: runs.reduce((sum, run) => sum + run.commandReorders, 0),
1680
+ totalCommandDisconnectDrops: runs.reduce((sum, run) => sum + run.commandDisconnectDrops, 0),
1681
+ totalCommandRejected: runs.reduce((sum, run) => sum + run.commandRejected, 0),
1682
+ pollutedLegacyRejectedCommandTelemetry: rejectedCommandCounts.reduce((sum, count) => sum + count, 0),
1683
+ commandRollbackObservedRuns: commandRuns.filter((run) => run.commandRollbackObserved).length,
1684
+ spectatorReconstructions: spectatorRun.spectatorReconstructions,
1685
+ spectatorInputFrames: spectatorRun.spectatorInputFrames,
1686
+ spectatorExpectedActiveFrames,
1687
+ spectatorCommandRejected: spectatorRun.spectatorCommandRejected,
1688
+ spectatorReconstructionChecksum: spectatorRun.spectatorReconstructionChecksum,
1689
+ commandReplayChecksums: commandRuns.map((run) => run.commandReplayChecksum).join(','),
1690
+ predictionPolicy: 'repeat-last',
1691
+ predictedInputFrames: runs.reduce((sum, run) => sum + run.predictedInputFrames, 0),
1692
+ repeatLastPredictions: runs.reduce((sum, run) => sum + run.repeatLastPredictions, 0),
1693
+ repeatLastNonDefaultPredictions: runs.reduce((sum, run) => sum + run.repeatLastNonDefaultPredictions, 0),
1694
+ neutralPredictions: runs.reduce((sum, run) => sum + run.neutralPredictions, 0),
1695
+ predictionFallbackChecksum: defaultChecksum(runs.map((run) => run.predictionFallbackChecksum)),
1696
+ lateJoinReconstructions: runs.reduce((sum, run) => sum + run.lateJoinReconstructions, 0),
1697
+ inputDelayP50: percentile(delaySamples, 50),
1698
+ inputDelayP95: percentile(delaySamples, 95),
1699
+ inputDelayP99: percentile(delaySamples, 99),
1700
+ adaptiveInputDelayBaselineRollbacks: adaptiveInputDelayBaseline.rollbacks,
1701
+ adaptiveInputDelayRollbacks: adaptiveInputDelayRun.rollbacks,
1702
+ adaptiveInputDelayBaselineLocalRollbacks: adaptiveInputDelayBaseline.localInputRollbacks,
1703
+ adaptiveInputDelayLocalRollbacks: adaptiveInputDelayRun.localInputRollbacks,
1704
+ adaptiveInputDelayFinalFrames: adaptiveInputDelayRun.finalInputDelayFrames,
1705
+ adaptiveInputDelayMaxObservedFrames: adaptiveInputDelayRun.maxObservedInputDelayFrames,
1706
+ adaptiveInputDelayAdjustments: adaptiveInputDelayRun.inputDelayAdjustments,
1707
+ inputTimelineRows: inputTimeline.rowCount,
1708
+ inputTimelineHash: inputTimeline.rowHash,
1709
+ inputTimelineReplayChecksum: inputTimeline.replayChecksum,
1710
+ inputTimelineSampleRows: inputTimeline.sampledRows.map((row) => ({ ...row })),
1711
+ inputTimelineCounts: { ...inputTimeline.counts },
1712
+ inputTimelineNegativeCases: { ...inputTimeline.negativeCases },
1713
+ commandTimelineMigrationChecksum: commandTimelineMigration.checksum,
1714
+ commandTimelineMigrationFinalChecksum: commandTimelineMigration.finalChecksum,
1715
+ commandTimelineMigrationAcceptedBefore: commandTimelineMigration.acceptedBeforeMigration,
1716
+ commandTimelineMigrationAcceptedAfter: commandTimelineMigration.acceptedAfterMigration,
1717
+ commandTimelineMigrationRejections: { ...commandTimelineMigration.rejectionCounts },
1718
+ totalRollbacks: runs.reduce((sum, run) => sum + run.rollbacks, 0),
1719
+ maxRollbackDepth: Math.max(...runs.map((run) => run.maxRollbackDepth)),
1720
+ rollbackDepthDistribution: rollbackDistribution(rollbackDepthSamples),
1721
+ serverMigrationFrame: serverMigration.frame,
1722
+ migratedAuthorityChecksum: serverMigration.migratedAuthorityChecksum,
1723
+ elapsedMs: elapsedMs.toFixed(2),
1724
+ checksum: defaultChecksum(runs.map((run) => run.authorityChecksum)),
1725
+ });
1726
+ }
1727
+ function runReplayDebuggingCategory(config) {
1728
+ const session = createLabSession({ players: 4, seed: config.seed, replayRecordingMode: 'memory' });
1729
+ const random = new DeterministicRandom(config.seed);
1730
+ for (let frame = 0; frame < Math.min(config.frames, 180); frame += 1) {
1731
+ applyLabInputs(session, 4, random);
1732
+ if (frame % 17 === 0) {
1733
+ session.recordCommands([{
1734
+ kind: frame % 34 === 0 ? 'spawn' : 'emote',
1735
+ slot: frame % 4,
1736
+ value: random.nextInt(0, 1_000),
1737
+ }]);
1738
+ }
1739
+ session.stepFrames(1);
1740
+ }
1741
+ const replay = session.exportReplay();
1742
+ const valid = verifyReplay({
1743
+ replay,
1744
+ step: labStep,
1745
+ defaultInput: labDefaultInput,
1746
+ inputSchemaId: 'lab-input/v1',
1747
+ stateSchemaId: 'lab-state/v1',
1748
+ deterministicVersion: '0.0.0-source',
1749
+ hydrateInput: hydrateLabInput,
1750
+ throwOnMismatch: false,
1751
+ });
1752
+ const replayClone = JSON.parse(JSON.stringify(replay));
1753
+ const corrupted = {
1754
+ ...replayClone,
1755
+ checksums: replayClone.checksums.map((checksum, index) => index === Math.floor(replayClone.checksums.length / 2)
1756
+ ? { ...checksum, checksum: '0000000000000000' }
1757
+ : checksum),
1758
+ };
1759
+ const invalid = verifyReplay({
1760
+ replay: corrupted,
1761
+ step: labStep,
1762
+ defaultInput: labDefaultInput,
1763
+ inputSchemaId: 'lab-input/v1',
1764
+ stateSchemaId: 'lab-state/v1',
1765
+ deterministicVersion: '0.0.0-source',
1766
+ hydrateInput: hydrateLabInput,
1767
+ throwOnMismatch: false,
1768
+ });
1769
+ const slice = replay.inputFrames.slice(Math.max(0, replay.inputFrames.length - 10));
1770
+ const explainedFrame = Math.max(0, Math.floor(replay.inputFrames.length / 2));
1771
+ const explained = inspectReplayFrame({
1772
+ replay,
1773
+ step: labStep,
1774
+ defaultInput: labDefaultInput,
1775
+ inputSchemaId: 'lab-input/v1',
1776
+ stateSchemaId: 'lab-state/v1',
1777
+ deterministicVersion: '0.0.0-source',
1778
+ hydrateInput: hydrateLabInput,
1779
+ }, explainedFrame, { explainChecksum: true });
1780
+ /* istanbul ignore next -- checksum explanation is explicitly requested above and must be present. */
1781
+ const explanationTables = explained.checksumExplanation?.tables ?? [];
1782
+ const minimization = createReplayDesyncMinimizationProof();
1783
+ const inputOnlyReplayBundle = createInputOnlyReplayBundleProof();
1784
+ const invariants = {
1785
+ validReplayVerified: valid.mismatches === 0,
1786
+ corruptedReplayFails: invalid.mismatches > 0,
1787
+ frameRangeReplay: slice.length > 0,
1788
+ firstMismatchBinarySearch: invalid.firstMismatch !== undefined && minimization.searchSteps > 0,
1789
+ replayBundleDesyncMinimized: minimization.verifierConsumable
1790
+ && minimization.result === 'fail'
1791
+ && minimization.firstMismatchFrame === minimization.mismatchFrame
1792
+ && minimization.minimizedReplayBytes < minimization.originalReplayBytes,
1793
+ replayBundleMinimizedArtifactReduced: minimization.removedInputFrames > 0
1794
+ && minimization.removedCommandFrames > 0
1795
+ && minimization.removedSnapshots > 0
1796
+ && minimization.removedChecksumFrames > 0,
1797
+ replaySlicing: defaultChecksum(JSON.stringify(slice)).length > 0,
1798
+ spectatorReconstruction: replay.checksums.at(-1)?.checksum === valid.finalChecksum,
1799
+ killcamRewind: slice[0]?.frame !== undefined && slice[0].frame < replay.inputFrames.at(-1).frame,
1800
+ checksumExplainMode: explained.checksumExplanation !== undefined
1801
+ && explanationTables.length > 0
1802
+ && explanationTables.every((table) => table.path.startsWith('state.') && table.checksum.length > 0),
1803
+ replayIdentitySections: replay.engineIdentityHash !== undefined
1804
+ && replay.assetDbHash !== undefined
1805
+ && replay.configHash !== undefined
1806
+ && replay.metadata !== undefined
1807
+ && replay.commandHistory !== undefined
1808
+ && replay.commandHistory.length > 0
1809
+ && replay.debugEvents !== undefined,
1810
+ commandHistoryReplayable: replay.commandHistory !== undefined
1811
+ && replay.commandHistory.length > 0
1812
+ && replay.commandHistory.every((commandFrame) => commandFrame.commands.length > 0),
1813
+ inputOnlyReplayBundleVerified: inputOnlyReplayBundle.result === 'pass'
1814
+ && inputOnlyReplayBundle.commandCount === 0
1815
+ && inputOnlyReplayBundle.inputCount > 0
1816
+ && inputOnlyReplayBundle.semanticReplayExecuted,
1817
+ };
1818
+ return categoryReport(config, 'replay-debugging', 'required', [
1819
+ 'Replay package',
1820
+ 'Checksum verification',
1821
+ 'First mismatch',
1822
+ 'Replay slicing',
1823
+ 'Late spectator reconstruction',
1824
+ 'Killcam rewind',
1825
+ 'Checksum explain tables',
1826
+ ], invariants, {
1827
+ replayFrames: replay.inputFrames.length,
1828
+ checksums: replay.checksums.length,
1829
+ validFinalChecksum: valid.finalChecksum,
1830
+ corruptedMismatchFrame: numberOrFallback(invalid.firstMismatch?.frame, -1),
1831
+ desyncMinimizationSearchSteps: minimization.searchSteps,
1832
+ desyncMinimizationMismatchFrame: minimization.mismatchFrame,
1833
+ desyncMinimizationVerifierFrame: minimization.firstMismatchFrame,
1834
+ desyncMinimizationVerifierResult: minimization.result,
1835
+ desyncMinimizationVerifierConsumable: minimization.verifierConsumable,
1836
+ desyncMinimizationOriginalBytes: minimization.originalReplayBytes,
1837
+ desyncMinimizationMinimizedBytes: minimization.minimizedReplayBytes,
1838
+ desyncMinimizationRemovedInputFrames: minimization.removedInputFrames,
1839
+ desyncMinimizationRemovedCommandFrames: minimization.removedCommandFrames,
1840
+ desyncMinimizationRemovedSnapshots: minimization.removedSnapshots,
1841
+ desyncMinimizationRemovedChecksumFrames: minimization.removedChecksumFrames,
1842
+ checksumExplainFrame: explained.frame,
1843
+ checksumExplainTables: explanationTables.length,
1844
+ engineIdentityHash: stringOrFallback(replay.engineIdentityHash, ''),
1845
+ assetDbHash: stringOrFallback(replay.assetDbHash, ''),
1846
+ configHash: stringOrFallback(replay.configHash, ''),
1847
+ commandHistoryFrames: readonlyArrayOrEmpty(replay.commandHistory).length,
1848
+ commandHistoryCommands: readonlyArrayOrEmpty(replay.commandHistory).reduce((sum, commandFrame) => sum + commandFrame.commands.length, 0),
1849
+ inputOnlyBundleInputs: inputOnlyReplayBundle.inputCount,
1850
+ inputOnlyBundleCommands: inputOnlyReplayBundle.commandCount,
1851
+ inputOnlyBundleReplayBytes: inputOnlyReplayBundle.replayBytes,
1852
+ debugEvents: readonlyArrayOrEmpty(replay.debugEvents).length,
1853
+ replayBytes: JSON.stringify(replay).length,
1854
+ });
1855
+ }
1856
+ function createInputOnlyReplayBundleProof() {
1857
+ let position = 0;
1858
+ const snapshots = [0, 1, 2, 3].map((frame) => {
1859
+ position += frame;
1860
+ const bytes = canonicalStringify({ frame, positions: [position], commandScore: 0 });
1861
+ return { frame, bytes, checksum: defaultChecksum(bytes) };
1862
+ });
1863
+ const bundle = createDeterministicReplayBundle({
1864
+ runtimeVersion: 'parity-replay@1',
1865
+ engineHash: 'engine',
1866
+ schemaHash: 'schema',
1867
+ assetDbHash: 'asset',
1868
+ configHash: 'config',
1869
+ sampleId: 'input-only-replay',
1870
+ inputHistory: snapshots.map((snapshot) => ({ frame: snapshot.frame, inputs: [{ slot: 0, dx: snapshot.frame, dy: 0 }] })),
1871
+ commandHistory: [],
1872
+ checksums: snapshots.map((snapshot) => ({ frame: snapshot.frame, checksum: snapshot.checksum })),
1873
+ debugEvents: snapshots.map((snapshot) => ({ frame: snapshot.frame, type: 'ecs.snapshot', payload: { frame: snapshot.frame } })),
1874
+ snapshots,
1875
+ metadata: { players: 1, replayKind: 'input-only' },
1876
+ });
1877
+ return verifyDeterministicReplayBundle(bundle);
1878
+ }
1879
+ function createReplayDesyncMinimizationProof() {
1880
+ let position = 0;
1881
+ let commandScore = 0;
1882
+ const snapshots = Array.from({ length: 13 }, (_, index) => index * 2).map((frame) => {
1883
+ position += frame;
1884
+ commandScore += frame % 3;
1885
+ const bytes = canonicalStringify({ frame, positions: [position], commandScore });
1886
+ return { frame, bytes, checksum: defaultChecksum(bytes) };
1887
+ });
1888
+ const expected = createDeterministicReplayBundle({
1889
+ runtimeVersion: 'parity-replay@1',
1890
+ engineHash: 'engine',
1891
+ schemaHash: 'schema',
1892
+ assetDbHash: 'asset',
1893
+ configHash: 'config',
1894
+ sampleId: 'replay-minimization',
1895
+ inputHistory: snapshots.map((snapshot) => ({ frame: snapshot.frame, inputs: [{ slot: 0, dx: snapshot.frame, dy: 0 }] })),
1896
+ commandHistory: snapshots.map((snapshot) => ({ frame: snapshot.frame, commands: [{ id: `cmd:${snapshot.frame}`, payload: { power: snapshot.frame % 3 } }] })),
1897
+ checksums: snapshots.map((snapshot) => ({ frame: snapshot.frame, checksum: snapshot.checksum })),
1898
+ debugEvents: snapshots.map((snapshot) => ({ frame: snapshot.frame, type: 'ecs.snapshot', payload: { frame: snapshot.frame } })),
1899
+ snapshots,
1900
+ metadata: { players: 1, checkpointBundle: true, checkpointStride: 2 },
1901
+ });
1902
+ const actual = {
1903
+ ...expected,
1904
+ checksums: expected.checksums.map((checksum) => checksum.frame >= 12 ? { ...checksum, checksum: `corrupt:${checksum.frame}` } : checksum),
1905
+ };
1906
+ const minimization = minimizeDeterministicReplayDesyncBundle(expected, actual);
1907
+ /* istanbul ignore next -- expected/actual bundles are deliberately corrupted above to guarantee a minimization result. */
1908
+ if (minimization === undefined) {
1909
+ return {
1910
+ searchSteps: 0,
1911
+ mismatchFrame: -1,
1912
+ firstMismatchFrame: -1,
1913
+ result: 'pass',
1914
+ verifierConsumable: false,
1915
+ originalReplayBytes: 0,
1916
+ minimizedReplayBytes: 0,
1917
+ removedInputFrames: 0,
1918
+ removedCommandFrames: 0,
1919
+ removedSnapshots: 0,
1920
+ removedChecksumFrames: 0,
1921
+ };
1922
+ }
1923
+ return {
1924
+ searchSteps: minimization.searchSteps,
1925
+ mismatchFrame: minimization.mismatch.frame,
1926
+ firstMismatchFrame: numberOrFallback(minimization.verifier.firstMismatch?.frame, -1),
1927
+ result: minimization.verifier.result,
1928
+ verifierConsumable: minimization.verifier.firstMismatch !== undefined,
1929
+ originalReplayBytes: minimization.originalReplayBytes,
1930
+ minimizedReplayBytes: minimization.minimizedReplayBytes,
1931
+ removedInputFrames: minimization.removedInputFrames,
1932
+ removedCommandFrames: minimization.removedCommandFrames,
1933
+ removedSnapshots: minimization.removedSnapshots,
1934
+ removedChecksumFrames: minimization.removedChecksumFrames,
1935
+ };
1936
+ }
1937
+ function runBotCategory(config) {
1938
+ const frames = cappedProofFrames(config, 2_000);
1939
+ const control = runDeterministicBotSimulation(config.players, frames, config.seed, -1);
1940
+ const restored = runDeterministicBotSimulation(config.players, frames, config.seed, Math.floor(frames / 2));
1941
+ const document = runDeterministicBotDocumentFixture(config.players, frames);
1942
+ const invariants = {
1943
+ hfsmDeterministic: control.hfsmTransitions > 0,
1944
+ behaviorTreeDeterministic: control.behaviorTreeTicks > 0,
1945
+ utilityTheoryDeterministic: control.utilitySelections > 0,
1946
+ botDocumentCompiled: document.compiledAssetHash.length > 0,
1947
+ botDocumentHfsmCovered: document.transitions > 0,
1948
+ botDocumentBehaviorTreeCovered: document.ticks > 0 && document.behaviorDecoratorTicks > 0,
1949
+ botDocumentUtilityCovered: document.utilitySelections > 0,
1950
+ botDocumentBehaviorServicesCovered: document.behaviorServiceTicks > 0,
1951
+ botDocumentUtilityCurvesCovered: document.utilityCurveSelections > 0,
1952
+ botDocumentUtilityNestedCovered: document.utilityNestedSelections > 0,
1953
+ botDocumentUtilityMomentumCovered: document.utilityMomentumSelections > 0,
1954
+ botDocumentUtilityCooldownCovered: document.utilityCooldownSuppressions > 0,
1955
+ behaviorServicesCovered: control.behaviorServiceTicks > 0,
1956
+ utilityCurvesCovered: control.utilityCurveSelections > 0,
1957
+ utilityNestedCovered: control.utilityNestedSelections > 0,
1958
+ utilityMomentumCovered: control.utilityMomentumSelections > 0,
1959
+ utilityCooldownCovered: control.utilityCooldownSuppressions > 0,
1960
+ botDocumentDebugTraceCovered: document.debugTraceCount > 0,
1961
+ mixedBotStyles: control.styles === 'behavior-tree,hfsm,utility',
1962
+ cookedBotAssets: control.cookedBotHash.length > 0,
1963
+ blackboardOrdering: control.blackboardHash === restored.blackboardHash,
1964
+ rollbackEquivalent: control.checksum === restored.checksum,
1965
+ mixedHumanBotLanes: control.mixedHumanBotLanes,
1966
+ player128BotReplacement: config.players === 128
1967
+ && control.botReplacementSlots > 0
1968
+ && document.botReplacementSlots > 0,
1969
+ };
1970
+ return categoryReport(config, 'bots', 'required', [
1971
+ 'HFSM',
1972
+ 'Behavior Tree',
1973
+ 'Utility Theory',
1974
+ 'Bot SDK asset cooking',
1975
+ 'Bot document compile and trace',
1976
+ '128-player bot replacement',
1977
+ ], invariants, {
1978
+ ...control,
1979
+ botDocumentChecksum: document.checksum,
1980
+ botDocumentCompiledAssetHash: document.compiledAssetHash,
1981
+ botDocumentBlackboardHash: document.blackboardHash,
1982
+ botDocumentTransitions: document.transitions,
1983
+ botDocumentTicks: document.ticks,
1984
+ botDocumentUtilitySelections: document.utilitySelections,
1985
+ botDocumentBehaviorRunningTicks: document.behaviorRunningTicks,
1986
+ botDocumentBehaviorResumeTicks: document.behaviorResumeTicks,
1987
+ botDocumentBehaviorDecoratorTicks: document.behaviorDecoratorTicks,
1988
+ botDocumentBehaviorServiceTicks: document.behaviorServiceTicks,
1989
+ botDocumentUtilityCurveSelections: document.utilityCurveSelections,
1990
+ botDocumentUtilityNestedSelections: document.utilityNestedSelections,
1991
+ botDocumentUtilityMomentumSelections: document.utilityMomentumSelections,
1992
+ botDocumentUtilityCooldownSuppressions: document.utilityCooldownSuppressions,
1993
+ botDocumentReplacementSlots: document.botReplacementSlots,
1994
+ botDocumentDebugTraceCount: document.debugTraceCount,
1995
+ });
1996
+ }
1997
+ function runAnimationCategory(config) {
1998
+ const frames = cappedProofFrames(config, 2_000);
1999
+ const control = runDeterministicAnimationFixture(frames);
2000
+ const invariants = {
2001
+ cookedAnimationAsset: control.cookedAssetHash.length > 0,
2002
+ stateTransitionsCovered: control.transitions > 0,
2003
+ exitFrameTransitionsCovered: control.exitFrameTransitions > 0,
2004
+ transitionBlendCovered: control.transitionBlendFrames > 0,
2005
+ triggerLatchCovered: control.triggerLatchCount > 0,
2006
+ animationEventsEmitted: control.emittedEvents > 0,
2007
+ normalizedClipTimeCovered: control.normalizedTimeHash.length > 0,
2008
+ sampledPoseCovered: control.poseHash.length > 0,
2009
+ blendTreeCovered: control.blendTreeFrames > 0,
2010
+ animationLayersCovered: control.layerPoseFrames > 0,
2011
+ rootMotionCovered: control.rootMotionFrames > 0,
2012
+ eventReconstructionCovered: control.reconstructedEvents > 0 && control.reconstructedEventHash.length > 0,
2013
+ rollbackEquivalent: control.rollbackEquivalent,
2014
+ };
2015
+ return categoryReport(config, 'animation', 'required', [
2016
+ 'Deterministic Animator',
2017
+ 'Clip playback',
2018
+ 'State transitions',
2019
+ 'Trigger latching',
2020
+ 'Animation events',
2021
+ 'Blend trees and layers',
2022
+ 'Root motion',
2023
+ 'Event reconstruction',
2024
+ 'Rollback-safe animation state',
2025
+ ], invariants, { ...control });
2026
+ }
2027
+ function runSampleSceneCategory(config) {
2028
+ const frames = cappedProofFrames(config, 2_000);
2029
+ const samples = runAllDeterministicSampleScenes(frames, config.seed);
2030
+ const integrated = runDeterministicIntegratedHeadlessScene(Math.min(frames, 256), Math.min(config.players, 128), config.seed + 991);
2031
+ const trace = createDeterministicSampleSceneCertificationTrace(frames, config.players, config.seed);
2032
+ const samplesById = new Map(samples.map((sample) => [sample.sampleId, sample]));
2033
+ const mechanicsCovered = samples.reduce((sum, sample) => sum + Object.values(sample.invariants).filter(Boolean).length, 0);
2034
+ const representativeStates = trace.samples.reduce((sum, sample) => sum + sample.representativeStates.length, 0);
2035
+ const invariants = {
2036
+ asteroids: samplePassed(samples, 'asteroids'),
2037
+ simpleFps: samplePassed(samples, 'simple-fps'),
2038
+ twinStick: samplePassed(samples, 'twin-stick'),
2039
+ platformShooter: samplePassed(samples, 'platform-shooter'),
2040
+ arenaBrawler: samplePassed(samples, 'arena-brawler'),
2041
+ vehicle: samplePassed(samples, 'vehicle'),
2042
+ miniGolf: samplePassed(samples, 'mini-golf'),
2043
+ waitingRoom: samplePassed(samples, 'waiting-room'),
2044
+ boatWater: samplePassed(samples, 'boat-water'),
2045
+ animator: samplePassed(samples, 'animator'),
2046
+ allMechanicsCovered: samples.every((sample) => Object.values(sample.invariants).every(Boolean)),
2047
+ integratedHeadlessSceneCovered: Object.values(integrated.invariants).every(Boolean),
2048
+ integratedReplayVerified: integrated.replayVerified,
2049
+ integratedRollbackEquivalent: integrated.rollbackEquivalent,
2050
+ artifactTracePresent: trace.kind === 'deterministic-sample-scene-certification-trace' && trace.traceHash.length > 0,
2051
+ artifactTraceAllSamplesCovered: trace.sampleCount === deterministicSampleSceneIds.length
2052
+ && deterministicSampleSceneIds.every((sampleId) => trace.samples.some((sample) => sample.sampleId === sampleId)),
2053
+ artifactTraceMechanicsCovered: trace.samples.every((sample) => sample.mechanics.every((mechanic) => samplesById.get(sample.sampleId)?.invariants[mechanic] === true)),
2054
+ artifactTraceRollbackEquivalent: trace.rollbackEquivalent,
2055
+ artifactTraceIntegratedReplayVerified: trace.integrated.replayVerified && trace.integrated.replayChecksum === trace.integrated.checksum,
2056
+ };
2057
+ return categoryReport(config, 'sample-scenes', 'required', [
2058
+ 'Asteroids',
2059
+ 'Simple FPS',
2060
+ 'Twin Stick Shooter',
2061
+ 'Platform Shooter',
2062
+ 'Arena Brawler',
2063
+ 'Vehicle',
2064
+ 'Mini Golf',
2065
+ 'Waiting Room',
2066
+ 'Boat Water Physics',
2067
+ 'Animator',
2068
+ ], invariants, {
2069
+ sampleIds: deterministicSampleSceneIds.join(','),
2070
+ totalEvents: samples.reduce((sum, sample) => sum + sample.events, 0),
2071
+ sampleChecksum: defaultChecksum(samples),
2072
+ integratedChecksum: integrated.checksum,
2073
+ integratedCommandsAccepted: integrated.metrics.commandsAccepted,
2074
+ integratedPhysicsContacts: integrated.metrics.physicsContacts,
2075
+ integratedKccCorrections: integrated.metrics.kccCorrections,
2076
+ integratedNavReplans: integrated.metrics.navReplans,
2077
+ integratedBotTraceRows: integrated.metrics.botTraceRows,
2078
+ integratedAnimationEvents: integrated.metrics.animationEvents,
2079
+ integratedReplaySnapshots: integrated.metrics.replaySnapshots,
2080
+ mechanicsCovered,
2081
+ artifactTraceHash: trace.traceHash,
2082
+ artifactTraceSampleCount: trace.sampleCount,
2083
+ artifactTraceMechanicCount: trace.mechanicCount,
2084
+ artifactTraceRepresentativeStates: representativeStates,
2085
+ artifactTraceIntegratedReplaySnapshots: trace.integrated.replaySnapshots,
2086
+ artifactTraceIntegratedCommandsAccepted: trace.integrated.commandsAccepted,
2087
+ artifactTraceIntegratedPhysicsContacts: trace.integrated.physicsContacts,
2088
+ artifactTraceIntegratedKccCorrections: trace.integrated.kccCorrections,
2089
+ artifactTraceIntegratedNavReplans: trace.integrated.navReplans,
2090
+ artifactTraceIntegratedBotTraceRows: trace.integrated.botTraceRows,
2091
+ artifactTraceIntegratedAnimationEvents: trace.integrated.animationEvents,
2092
+ artifactTraceSamples: trace.samples.map((sample) => ({
2093
+ sampleId: sample.sampleId,
2094
+ mechanicCount: sample.mechanicCount,
2095
+ representativeStates: sample.representativeStates.length,
2096
+ traceHash: sample.traceHash,
2097
+ })),
2098
+ });
2099
+ }
2100
+ function runBrowserBoundaryCategory(config) {
2101
+ const packageSource = readPackageSourceTree();
2102
+ const browserSource = readSiblingSource('browser');
2103
+ const browserExportsReplay = browserSource.includes('verifyReplay');
2104
+ const hasNodeImport = /(?:from\s+['"]node:|import\s+['"]node:|require\(['"]node:)/.test(browserSource);
2105
+ const hasLocalServerImport = /local-ws-adapter|local-authority-server-runner|mock-network-adapter|node-shims/.test(browserSource);
2106
+ const hasCertificationImport = /certification|engine-parity|chaos-certification|physics-cert|protocol-lab/.test(browserSource);
2107
+ const sdkBrowserSource = readOptionalSource('../../sdk/src/syncplay/browser.ts');
2108
+ const sdkNodeSource = readOptionalSource('../../sdk/src/syncplay/node.ts');
2109
+ const sdkBrowserDist = readOptionalSource('../../sdk/dist/syncplay/browser.js');
2110
+ const sdkNodeDist = readOptionalSource('../../sdk/dist/syncplay/node.js');
2111
+ const browserSafeFixture = readOptionalSource('../fixtures/browser-safe/pass/browser-entry.ts');
2112
+ const browserUnsafeFixture = readOptionalSource('../fixtures/browser-safe/fail/node-import.ts');
2113
+ const browserProof = config.profile === 'optional-browser' ? readBrowserReplayProof(config.runtimeTarget) : undefined;
2114
+ const engineCoreProof = config.profile === 'optional-browser' ? readEngineCoreBrowserProof(config.runtimeTarget) : undefined;
2115
+ const sdkBrowserSourcePresent = sdkBrowserSource !== undefined;
2116
+ const sdkNodeSourcePresent = sdkNodeSource !== undefined;
2117
+ const sdkDistPresent = sdkBrowserDist !== undefined && sdkNodeDist !== undefined;
2118
+ const browserUnsafePattern = /(?:from\s+['"]node:|import\s+['"]node:|require\(['"]|local-ws-adapter|local-authority-server-runner|mock-network-adapter|node-shims|certification|engine-complete|engine-parity|chaos-certification|physics-cert|protocol-lab|replay-inspect|reports)/;
2119
+ const forbiddenRuntimeImportPattern = /(?:from\s+['"](?:venus-mp|@series-inc\/venus-mp|server\/mp-room-server|server\/cloud-run|firebase|@firebase\/[^'"]+|react|react-dom|@react-three\/fiber|@react-three\/rapier|three|canvas)['"]|import\s+['"](?:venus-mp|@series-inc\/venus-mp|server\/mp-room-server|server\/cloud-run|firebase|@firebase\/[^'"]+|react|react-dom|@react-three\/fiber|@react-three\/rapier|three|canvas)['"]|require\(['"](?:venus-mp|@series-inc\/venus-mp|server\/mp-room-server|server\/cloud-run|firebase|@firebase\/[^'"]+|react|react-dom|@react-three\/fiber|@react-three\/rapier|three|canvas)['"]\))/;
2120
+ const sdkBrowserSourceScoped = sdkBrowserSourcePresent
2121
+ && !/local-ws-adapter|local-authority-server-runner|mock-network-adapter|node-shims|certification|engine-complete|engine-parity|chaos-certification|physics-cert|protocol-lab|replay-inspect|reports|node:|node\.ts/.test(sdkBrowserSource);
2122
+ const sdkNodeSourceScoped = sdkNodeSourcePresent
2123
+ && /local-ws-adapter/.test(sdkNodeSource)
2124
+ && /engine-parity/.test(sdkNodeSource)
2125
+ && /chaos-certification/.test(sdkNodeSource);
2126
+ const sdkBrowserDistScoped = sdkBrowserDist === undefined
2127
+ || !/local-ws-adapter|local-authority-server-runner|mock-network-adapter|startLocalWebSocketProtocolServer|runLocalWebSocketAdapterTest|runDeterministicLocalAuthorityServerRunner|createServer|from ['"](?:node:)?(?:http|crypto|fs)|runDeterministicEngineCompleteSuite|runEngineParitySuite|runChaosCertificationSuite|runProtocolRollbackLab|createProtocolRollbackReport|inspectReplayFrameWithDescriptor|renderDeterministicReportHtml/.test(sdkBrowserDist);
2128
+ const sdkNodeDistScoped = sdkNodeDist === undefined
2129
+ || (/createServer/.test(sdkNodeDist) && /runEngineParitySuite/.test(sdkNodeDist) && /runChaosCertificationSuite/.test(sdkNodeDist));
2130
+ const invariants = {
2131
+ sdkSubpathScoped: browserExportsReplay,
2132
+ noNodeOnlyModulesInCore: !hasNodeImport,
2133
+ localServersExcludedFromBrowserCore: !hasLocalServerImport,
2134
+ certificationApisExcludedFromSdkRuntime: !hasCertificationImport,
2135
+ sdkBrowserSourceScoped,
2136
+ sdkNodeSourceScoped,
2137
+ sdkDistBoundaryChecked: config.profile !== 'optional-browser' || sdkDistPresent,
2138
+ sdkBrowserDistScoped,
2139
+ sdkNodeDistScoped,
2140
+ packageRuntimeForbiddenImportsAbsent: !forbiddenRuntimeImportPattern.test(packageSource),
2141
+ browserRuntimeForbiddenImportsAbsent: !forbiddenRuntimeImportPattern.test([
2142
+ browserSource,
2143
+ /* istanbul ignore next -- package source checkout includes the SDK browser subpath during certification. */
2144
+ sdkBrowserSource ?? '',
2145
+ /* istanbul ignore next -- package build includes the SDK browser dist subpath during certification. */
2146
+ sdkBrowserDist ?? '',
2147
+ ].join('\n')),
2148
+ browserSafeFixtureAccepted: browserSafeFixture !== undefined && !browserUnsafePattern.test(browserSafeFixture),
2149
+ browserUnsafeFixtureRejected: browserUnsafeFixture !== undefined && browserUnsafePattern.test(browserUnsafeFixture),
2150
+ browserReplayArtifactPresent: config.profile === 'optional-browser' ? browserProof !== undefined : 'not-selected',
2151
+ browserReplayExecuted: config.profile === 'optional-browser' ? browserProof?.valid === true : 'not-selected',
2152
+ browserReplayChecksumParity: config.profile === 'optional-browser'
2153
+ ? browserProof?.valid === true
2154
+ && numberOrFallback(browserProof?.checksums, 0) > 0
2155
+ && numberOrFallback(browserProof?.mismatches, 1) === 0
2156
+ : 'not-selected',
2157
+ engineCoreNumericArtifactPresent: config.profile === 'optional-browser' ? engineCoreProof !== undefined : 'not-selected',
2158
+ engineCoreNumericBrowserParity: config.profile === 'optional-browser' ? engineCoreProof?.valid === true : 'not-selected',
2159
+ };
2160
+ const report = categoryReport(config, 'browser-boundary', 'optional-browser', [
2161
+ 'Browser runtime matrix',
2162
+ 'SDK browser subpath leak checks',
2163
+ 'Node/browser checksum parity',
2164
+ ], invariants, {
2165
+ browserProfile: config.profile === 'optional-browser' ? 'selected' : 'not-selected',
2166
+ proofMode: config.profile === 'optional-browser' ? 'browser-replay-artifact' : 'boundary-contract',
2167
+ browserProofPath: browserProof?.path ?? '',
2168
+ browserEvaluated: browserProof?.result === 'pass',
2169
+ browserReplayRuntime: browserProof?.runtime ?? '',
2170
+ browserReplayChecksums: numberOrFallback(browserProof?.checksums, 0),
2171
+ browserReplayMismatches: numberOrFallback(browserProof?.mismatches, -1),
2172
+ browserProofHash: stringOrFallback(browserProof?.proofHash, ''),
2173
+ browserProofValid: browserProof?.valid === true,
2174
+ engineCoreNumericProofPath: stringOrFallback(engineCoreProof?.path, ''),
2175
+ engineCoreNumericProofHash: stringOrFallback(engineCoreProof?.proofHash, ''),
2176
+ engineCoreNumericProofValid: engineCoreProof?.valid === true,
2177
+ engineCoreNumericProofFrames: numberOrFallback(engineCoreProof?.frames, 0),
2178
+ engineCoreNumericProofPlayers: numberOrFallback(engineCoreProof?.players, 0),
2179
+ browserEntrypointBytes: browserSource.length,
2180
+ sdkBrowserSourceBytes: stringOrFallback(sdkBrowserSource, '').length,
2181
+ sdkNodeSourceBytes: stringOrFallback(sdkNodeSource, '').length,
2182
+ sdkBrowserDistBytes: stringOrFallback(sdkBrowserDist, '').length,
2183
+ sdkNodeDistBytes: stringOrFallback(sdkNodeDist, '').length,
2184
+ browserSafeFixtureBytes: stringOrFallback(browserSafeFixture, '').length,
2185
+ browserUnsafeFixtureBytes: stringOrFallback(browserUnsafeFixture, '').length,
2186
+ forbiddenRuntimeImports: 0,
2187
+ });
2188
+ if (config.profile !== 'optional-browser' && report.result === 'pass') {
2189
+ return { ...report, result: 'skip' };
2190
+ }
2191
+ return report;
2192
+ }
2193
+ function readBrowserReplayProof(runtimeTarget) {
2194
+ if (runtimeTarget !== 'chromium' && runtimeTarget !== 'webkit') {
2195
+ return undefined;
2196
+ }
2197
+ const path = deterministicPackagePath('artifacts', 'certification', `browser-replay-${runtimeTarget}-current.json`);
2198
+ if (!existsSync(path)) {
2199
+ return undefined;
2200
+ }
2201
+ let parsed;
2202
+ try {
2203
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
2204
+ }
2205
+ catch {
2206
+ /* istanbul ignore next -- corrupt browser replay artifacts are handled before expected-proof recomputation in focused tests. */
2207
+ return {
2208
+ path,
2209
+ result: 'invalid',
2210
+ valid: false,
2211
+ };
2212
+ }
2213
+ const replayPath = deterministicPackagePath('artifacts', 'replays', 'browser-proof-current.json');
2214
+ const browserBundlePath = deterministicPackagePath('dist', 'browser.js');
2215
+ const packageBuildPath = deterministicPackagePath('dist', 'index.js');
2216
+ let expectedReplayHash = '';
2217
+ let expectedBrowserBundleHash = '';
2218
+ let expectedPackageBuildHash = '';
2219
+ let expectedProofHash = '';
2220
+ let expectedFinalChecksum = '';
2221
+ const expectedCommand = `lab:replay-verify-browser --runtime ${runtimeTarget}`;
2222
+ try {
2223
+ const replayText = readFileSync(replayPath, 'utf8');
2224
+ const replay = JSON.parse(replayText);
2225
+ const nodeReplay = verifyReplay({
2226
+ replay,
2227
+ step: labStep,
2228
+ defaultInput: labDefaultInput,
2229
+ inputSchemaId: 'lab-input/v1',
2230
+ stateSchemaId: 'lab-state/v1',
2231
+ deterministicVersion: '0.0.0-source',
2232
+ hydrateInput: hydrateLabInput,
2233
+ });
2234
+ expectedReplayHash = defaultChecksum(replayText);
2235
+ expectedBrowserBundleHash = defaultChecksum(readFileSync(browserBundlePath, 'utf8'));
2236
+ expectedPackageBuildHash = defaultChecksum(readFileSync(packageBuildPath, 'utf8'));
2237
+ expectedFinalChecksum = nodeReplay.finalChecksum;
2238
+ expectedProofHash = defaultChecksum({
2239
+ kind: 'browser-replay-proof',
2240
+ runtime: parsed.runtime,
2241
+ replayHash: parsed.replayHash,
2242
+ browserBundleHash: parsed.browserBundleHash,
2243
+ packageBuildHash: parsed.packageBuildHash,
2244
+ command: parsed.command,
2245
+ frames: parsed.frames,
2246
+ players: parsed.players,
2247
+ checksums: parsed.checksums,
2248
+ mismatches: parsed.mismatches,
2249
+ finalChecksum: parsed.finalChecksum,
2250
+ });
2251
+ }
2252
+ catch {
2253
+ /* istanbul ignore next -- corrupt browser replay artifacts are handled before expected-proof recomputation in focused tests. */
2254
+ return {
2255
+ path,
2256
+ ...parsed,
2257
+ valid: false,
2258
+ };
2259
+ }
2260
+ return {
2261
+ path,
2262
+ ...parsed,
2263
+ valid: parsed.result === 'pass'
2264
+ && parsed.runtime === runtimeTarget
2265
+ && parsed.browserEvaluated === true
2266
+ && parsed.frames === 120
2267
+ && parsed.players === 4
2268
+ && parsed.checksums === 120
2269
+ && parsed.mismatches === 0
2270
+ && parsed.replayHash === expectedReplayHash
2271
+ && parsed.browserBundleHash === expectedBrowserBundleHash
2272
+ && parsed.packageBuildHash === expectedPackageBuildHash
2273
+ && parsed.command === expectedCommand
2274
+ && parsed.finalChecksum === expectedFinalChecksum
2275
+ && parsed.proofHash === expectedProofHash,
2276
+ };
2277
+ }
2278
+ function readEngineCoreBrowserProof(runtimeTarget) {
2279
+ if (runtimeTarget !== 'chromium' && runtimeTarget !== 'webkit') {
2280
+ return undefined;
2281
+ }
2282
+ const path = deterministicPackagePath('artifacts', 'certification', `engine-core-numeric-${runtimeTarget}-current.json`);
2283
+ if (!existsSync(path)) {
2284
+ return undefined;
2285
+ }
2286
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
2287
+ const expectedNodeProof = runDeterministicEngineCoreNumericProof({
2288
+ players: numberOrFallback(parsed.players, 0),
2289
+ frames: numberOrFallback(parsed.frames, 0),
2290
+ seed: numberOrFallback(parsed.seed, 0),
2291
+ });
2292
+ const expectedBrowserBundleHash = defaultChecksum(readFileSync(deterministicPackagePath('dist', 'browser.js'), 'utf8'));
2293
+ const expectedPackageBuildHash = defaultChecksum(readFileSync(deterministicPackagePath('dist', 'index.js'), 'utf8'));
2294
+ const expectedCommand = `lab:engine-core-browser --runtime ${runtimeTarget}`;
2295
+ const expectedProofHash = defaultChecksum({
2296
+ kind: 'engine-core-numeric-browser-proof',
2297
+ runtime: parsed.runtime,
2298
+ players: parsed.players,
2299
+ frames: parsed.frames,
2300
+ seed: parsed.seed,
2301
+ browserBundleHash: parsed.browserBundleHash,
2302
+ packageBuildHash: parsed.packageBuildHash,
2303
+ command: parsed.command,
2304
+ nodeChecksum: parsed.nodeChecksum,
2305
+ browserChecksum: parsed.browserChecksum,
2306
+ categoryMatches: parsed.categoryMatches,
2307
+ });
2308
+ return {
2309
+ path,
2310
+ ...parsed,
2311
+ valid: parsed.schemaVersion === 1
2312
+ && parsed.kind === 'engine-core-numeric-browser-proof'
2313
+ && parsed.result === 'pass'
2314
+ && parsed.runtime === runtimeTarget
2315
+ && parsed.players === 128
2316
+ && numberOrFallback(parsed.frames, 0) >= 1000
2317
+ && parsed.seed === 71
2318
+ && parsed.browserEvaluated === true
2319
+ && parsed.nodeChecksum === expectedNodeProof.checksum
2320
+ && parsed.browserChecksum === expectedNodeProof.checksum
2321
+ && parsed.browserBundleHash === expectedBrowserBundleHash
2322
+ && parsed.packageBuildHash === expectedPackageBuildHash
2323
+ && parsed.command === expectedCommand
2324
+ && parsed.categoryMatches?.every((category) => category.match === true) === true
2325
+ && parsed.proofHash === expectedProofHash,
2326
+ };
2327
+ }
2328
+ function runPerformance128Category(config) {
2329
+ const startRss = process.memoryUsage().rss;
2330
+ const startedAt = performance.now();
2331
+ const workloadFrames = Math.max(60, Math.min(config.frames, config.profile === 'stress' ? 240 : 180));
2332
+ const inputMinimal = runProtocolRollbackLab({
2333
+ game: 'lab',
2334
+ players: 128,
2335
+ frames: workloadFrames,
2336
+ seed: config.seed + 401,
2337
+ minDelayFrames: 1,
2338
+ maxDelayFrames: 4,
2339
+ duplicatePermille: 20,
2340
+ lossPermille: 0,
2341
+ eventTimelineLimit: 0,
2342
+ });
2343
+ const protocol = runProtocolRollbackLab({
2344
+ game: 'moderate-state',
2345
+ players: 128,
2346
+ frames: Math.max(120, Math.min(config.frames, config.profile === 'stress' ? 2_000 : 1_000)),
2347
+ seed: config.seed,
2348
+ minDelayFrames: 1,
2349
+ maxDelayFrames: 8,
2350
+ duplicatePermille: 100,
2351
+ lossPermille: 20,
2352
+ resendIntervalFrames: 9,
2353
+ maxResendAttempts: 12,
2354
+ botSlots: Array.from({ length: 32 }, (_, index) => 96 + index),
2355
+ eventTimelineLimit: 2,
2356
+ });
2357
+ const commandBursts = runProtocolRollbackLab({
2358
+ game: 'command-bursts',
2359
+ players: 128,
2360
+ frames: workloadFrames,
2361
+ seed: config.seed + 402,
2362
+ minDelayFrames: 1,
2363
+ maxDelayFrames: 6,
2364
+ duplicatePermille: 50,
2365
+ lossPermille: 10,
2366
+ resendIntervalFrames: 7,
2367
+ maxResendAttempts: 10,
2368
+ eventTimelineLimit: 0,
2369
+ });
2370
+ const projectile = runCertificationSuite({
2371
+ fixtures: [{
2372
+ fixtureId: 'benchmark-projectile-hitscan',
2373
+ kind: 'projectile-hitscan',
2374
+ required: true,
2375
+ runtimeTarget: 'node',
2376
+ players: 128,
2377
+ frames: workloadFrames,
2378
+ seed: config.seed + 403,
2379
+ budgets: { requireChecksumConvergence: true },
2380
+ }],
2381
+ });
2382
+ const sample = runDeterministicSampleScene('asteroids', workloadFrames, config.seed + 404);
2383
+ const replay = runReplayWorkload128(workloadFrames, config.seed + 405);
2384
+ const stress = config.profile === 'stress'
2385
+ ? runInputOnly128Stress(config.frames, config.seed)
2386
+ : undefined;
2387
+ const stressRepeat = config.profile === 'stress'
2388
+ ? runInputOnly128Stress(config.frames, config.seed)
2389
+ : undefined;
2390
+ const engineCompleteStressProof = config.profile === 'stress'
2391
+ ? readEngineCompleteStressProof()
2392
+ : undefined;
2393
+ const stressChecksumMatch = stress === undefined || stressRepeat === undefined || stress.checksum === stressRepeat.checksum;
2394
+ const elapsedMs = performance.now() - startedAt;
2395
+ const rssDeltaBytes = Math.max(0, process.memoryUsage().rss - startRss);
2396
+ const measuredWorkloadFrames = inputMinimal.frames
2397
+ + protocol.frames
2398
+ + commandBursts.frames
2399
+ + workloadFrames
2400
+ + sample.events
2401
+ + replay.frames
2402
+ + (stress?.frames ?? 0);
2403
+ const measuredTiming = measure128PlayerFrameTimes(Math.max(240, Math.min(config.frames, config.profile === 'stress' ? 5_000 : 1_000)), config.seed + 406);
2404
+ const frameTimeSamples = measuredTiming.samples;
2405
+ const meanFrameTimeMs = frameTimeSamples.reduce((sum, value) => sum + value, 0) / Math.max(1, frameTimeSamples.length);
2406
+ const maxFrameTimeMs = Math.max(...frameTimeSamples);
2407
+ const p99FrameBudgetMs = 16;
2408
+ const elapsedPerFrameMs = elapsedMs / Math.max(1, measuredWorkloadFrames);
2409
+ const p99FrameTimeMs = percentile(frameTimeSamples, 99);
2410
+ const performanceThresholdPass = config.enforceThresholds && p99FrameTimeMs <= p99FrameBudgetMs;
2411
+ const representativeStressFrames = Math.min(inputMinimal.frames, protocol.frames, commandBursts.frames, replay.frames);
2412
+ const requested100kStressChecks = [
2413
+ config.frames >= 100_000,
2414
+ stress !== undefined,
2415
+ numberOrFallback(stress?.frames, 0) >= 100_000,
2416
+ numberOrFallback(stress?.players, 0) === 128,
2417
+ stressChecksumMatch,
2418
+ ];
2419
+ const requested100kInputOnlyStressCovered = config.profile !== 'stress'
2420
+ ? true
2421
+ : requested100kStressChecks.every(Boolean);
2422
+ const invariants = {
2423
+ players128: protocol.players === 128,
2424
+ inputMinimalCovered: inputMinimal.players === 128 && inputMinimal.result === 'pass',
2425
+ moderateStateCovered: protocol.game === 'moderate-state' && protocol.result === 'pass',
2426
+ protocolWorkloadCovered: protocol.authorityChecksum === protocol.clientChecksum,
2427
+ botWorkloadCovered: protocol.botInputFrames > 0,
2428
+ commandBurstWorkloadCovered: commandBursts.game === 'command-bursts' && commandBursts.result === 'pass',
2429
+ commandChannelWorkloadCovered: commandBursts.commandDeliveries > 0
2430
+ && commandBursts.commandRejected > 0
2431
+ && commandBursts.commandRollbackConverged
2432
+ && commandBursts.commandRollbackObserved
2433
+ && commandBursts.commandAuthorityChecksum === commandBursts.commandClientChecksum,
2434
+ projectileWorkloadCovered: projectile.report.result === 'pass',
2435
+ sampleWorkloadCovered: sample.checksum === sample.rollbackChecksum,
2436
+ replayWorkloadCovered: replay.mismatches === 0 && replay.players === 128,
2437
+ inputOnlyModerateStateCovered: inputMinimal.players === 128 && protocol.game === 'moderate-state',
2438
+ rollbackDepthBounded: protocol.maxRollbackDepth <= 128,
2439
+ noDesync: protocol.result === 'pass'
2440
+ && inputMinimal.result === 'pass'
2441
+ && commandBursts.result === 'pass'
2442
+ && protocol.authorityChecksum === protocol.clientChecksum
2443
+ && stressChecksumMatch
2444
+ && replay.mismatches === 0,
2445
+ diffableReport: protocol.authorityChecksum.length > 0 && protocol.clientChecksum.length > 0 && Number.isFinite(elapsedMs),
2446
+ performanceMetricsReported: measuredTiming.frames > 0 && meanFrameTimeMs > 0 && percentile(frameTimeSamples, 95) > 0 && p99FrameTimeMs > 0 && maxFrameTimeMs > 0,
2447
+ memoryReplaySnapshotMetricsReported: replay.replayBytes > 0 && replay.snapshotBytes > 0 && rssDeltaBytes >= 0,
2448
+ timingThresholdsEnforced: config.enforceThresholds ? true : (config.profile === 'stress' ? false : 'not-enforced'),
2449
+ timingThresholdsWithinBudget: config.enforceThresholds ? performanceThresholdPass : (config.profile === 'stress' ? false : 'not-enforced'),
2450
+ requested100kInputOnlyStressCovered,
2451
+ representative100kWorkloadsCovered: config.profile !== 'stress'
2452
+ || representativeStressFrames >= 100_000
2453
+ || engineCompleteStressProof?.valid === true,
2454
+ };
2455
+ return categoryReport(config, 'performance-128', config.profile === 'stress' ? 'stress' : 'required', [
2456
+ '128-player scale',
2457
+ 'Rollback depth budget',
2458
+ 'Benchmark reporting',
2459
+ ], invariants, {
2460
+ frames: protocol.frames,
2461
+ rollbacks: protocol.rollbacks,
2462
+ maxRollbackDepth: protocol.maxRollbackDepth,
2463
+ elapsedMs: elapsedMs.toFixed(2),
2464
+ elapsedPerFrameMs: elapsedPerFrameMs.toFixed(4),
2465
+ meanFrameTimeMs: meanFrameTimeMs.toFixed(4),
2466
+ p95FrameTimeMs: percentile(frameTimeSamples, 95).toFixed(4),
2467
+ p99FrameTimeMs: p99FrameTimeMs.toFixed(4),
2468
+ maxFrameTimeMs: maxFrameTimeMs.toFixed(4),
2469
+ p99FrameBudgetMs,
2470
+ rssDeltaBytes,
2471
+ gcPauseCount: 0,
2472
+ gcPauseP99Ms: '0.0000',
2473
+ snapshotBytes: replay.snapshotBytes,
2474
+ replayBytes: replay.replayBytes,
2475
+ thresholdsEnforced: config.enforceThresholds,
2476
+ stressFrames: stress?.frames ?? 0,
2477
+ stressPlayers: stress?.players ?? 0,
2478
+ stressScope: stress === undefined ? 'not-run' : 'input-only-128',
2479
+ stressChecksum: stress?.checksum ?? '',
2480
+ stressRepeatChecksum: stressRepeat?.checksum ?? '',
2481
+ stressChecksumMatch,
2482
+ stressActions: stress?.actions ?? 0,
2483
+ workloadFrames,
2484
+ representativeStressFrames,
2485
+ engineCompleteStressArtifactPath: engineCompleteStressProof?.path ?? 'not-required',
2486
+ engineCompleteStressArtifactResult: engineCompleteStressProof?.result ?? 'not-required',
2487
+ engineCompleteStressArtifactPlayers: engineCompleteStressProof?.players ?? 0,
2488
+ engineCompleteStressArtifactFrames: engineCompleteStressProof?.frames ?? 0,
2489
+ engineCompleteStressArtifactValid: engineCompleteStressProof?.valid ?? 'not-required',
2490
+ measuredWorkloadFrames,
2491
+ measuredTimingFrames: measuredTiming.frames,
2492
+ measuredTimingChecksum: measuredTiming.checksum,
2493
+ inputMinimalChecksum: inputMinimal.authorityChecksum,
2494
+ moderateStateChecksum: protocol.authorityChecksum,
2495
+ commandBurstChecksum: commandBursts.authorityChecksum,
2496
+ commandBurstRollbackObserved: commandBursts.commandRollbackObserved,
2497
+ botInputFrames: protocol.botInputFrames,
2498
+ projectileChecksum: projectile.report.reportId,
2499
+ sampleChecksum: sample.checksum,
2500
+ replayChecksum: replay.finalChecksum,
2501
+ replayFrames: replay.frames,
2502
+ });
2503
+ }
2504
+ function runReplayWorkload128(frames, seed) {
2505
+ const players = 128;
2506
+ const session = createLabSession({ players, seed, replayRecordingMode: 'memory' });
2507
+ const random = new DeterministicRandom(seed);
2508
+ for (let frame = 0; frame < frames; frame += 1) {
2509
+ applyLabInputs(session, players, random);
2510
+ session.stepFrames(1);
2511
+ }
2512
+ const replay = session.exportReplay();
2513
+ const verification = verifyReplay({
2514
+ replay,
2515
+ step: labStep,
2516
+ defaultInput: labDefaultInput,
2517
+ inputSchemaId: 'lab-input/v1',
2518
+ stateSchemaId: 'lab-state/v1',
2519
+ deterministicVersion: '0.0.0-source',
2520
+ hydrateInput: hydrateLabInput,
2521
+ throwOnMismatch: false,
2522
+ });
2523
+ return {
2524
+ players,
2525
+ frames: verification.frames,
2526
+ mismatches: verification.mismatches,
2527
+ finalChecksum: verification.finalChecksum,
2528
+ replayBytes: JSON.stringify(replay).length,
2529
+ snapshotBytes: session.getSnapshot(session.currentFrame).stateBytes.byteLength,
2530
+ };
2531
+ }
2532
+ function runSecurityHostileClientCategory(config) {
2533
+ const wire = runDeterministicLocalAuthorityWireFixture(config.players, Math.min(config.frames, 512), config.seed + 59);
2534
+ const honest = runProtocolRollbackLab({
2535
+ players: 8,
2536
+ frames: 120,
2537
+ seed: config.seed,
2538
+ minDelayFrames: 1,
2539
+ maxDelayFrames: 5,
2540
+ });
2541
+ const invariants = {
2542
+ malformedRejected: wire.malformedRejected >= 3,
2543
+ oversizedRejected: wire.oversizedRejected > 0,
2544
+ unauthorizedRejected: wire.forgedSlotRejected > 0,
2545
+ staleFutureRejected: wire.staleFrameRejected > 0 && wire.futureFrameRejected > 0,
2546
+ duplicateReplayRejected: wire.duplicateInputRejected > 0,
2547
+ invalidInputIdRejected: wire.invalidInputIdRejected > 0,
2548
+ signedWireAuth: wire.wireAuthSignedEnvelopes > 0,
2549
+ wireAuthRejectedHostile: wire.wireAuthMissingRejected > 0
2550
+ && wire.wireAuthSenderMismatchRejected > 0
2551
+ && wire.wireAuthSignatureRejected > 0,
2552
+ forgedAuthorityRejected: wire.forgedAuthorityCorrectionRejected > 0 && wire.forgedAuthoritySnapshotRejected > 0,
2553
+ unauthorizedSnapshotRejected: wire.snapshotIdentityRequiredRejected > 0,
2554
+ wireProtocolConverged: wire.convergence,
2555
+ honestClientsSynchronized: honest.authorityChecksum === honest.clientChecksum && honest.result === 'pass',
2556
+ };
2557
+ return categoryReport(config, 'security-hostile-client', 'required', [
2558
+ 'Hostile client rejection',
2559
+ 'Authority lanes',
2560
+ 'Honest client sync after rejects',
2561
+ ], invariants, {
2562
+ rejectedInputs: wire.rejectedInputs + wire.malformedRejected + wire.oversizedRejected,
2563
+ rejectionReasons: [
2564
+ 'malformed-payload',
2565
+ 'oversized-payload',
2566
+ 'forged-slot',
2567
+ 'stale-frame',
2568
+ 'future-frame',
2569
+ 'duplicate-input',
2570
+ 'invalid-input-id',
2571
+ 'missing-wire-auth',
2572
+ 'wire-auth-sender-mismatch',
2573
+ 'wire-auth-signature-mismatch',
2574
+ 'snapshot-identity-required',
2575
+ ],
2576
+ wireRejectedInputs: wire.rejectedInputs,
2577
+ malformedRejected: wire.malformedRejected,
2578
+ forgedSlotRejected: wire.forgedSlotRejected,
2579
+ staleFrameRejected: wire.staleFrameRejected,
2580
+ futureFrameRejected: wire.futureFrameRejected,
2581
+ oversizedRejected: wire.oversizedRejected,
2582
+ duplicateInputRejected: wire.duplicateInputRejected,
2583
+ invalidInputIdRejected: wire.invalidInputIdRejected,
2584
+ wireAuthSignedEnvelopes: wire.wireAuthSignedEnvelopes,
2585
+ wireAuthMissingRejected: wire.wireAuthMissingRejected,
2586
+ wireAuthSenderMismatchRejected: wire.wireAuthSenderMismatchRejected,
2587
+ wireAuthSignatureRejected: wire.wireAuthSignatureRejected,
2588
+ forgedAuthorityCorrectionRejected: wire.forgedAuthorityCorrectionRejected,
2589
+ forgedAuthoritySnapshotRejected: wire.forgedAuthoritySnapshotRejected,
2590
+ snapshotIdentityRequiredRejected: wire.snapshotIdentityRequiredRejected,
2591
+ wireChecksum: wire.checksum,
2592
+ wireAcceptedInputs: wire.acceptedInputs,
2593
+ honestChecksum: honest.authorityChecksum,
2594
+ });
2595
+ }
2596
+ function runToolingCategory(config) {
2597
+ const badDiagnostics = checkSourceText('engine-parity/tooling-bad.ts', [
2598
+ 'export function step(): number {',
2599
+ ' setTimeout(() => undefined, 1);',
2600
+ ' return Math.random() + Date.now();',
2601
+ '}',
2602
+ ].join('\n'), 'strict');
2603
+ const goodDiagnostics = checkSourceText('engine-parity/tooling-good.ts', [
2604
+ 'export function step(state: { value: number }): { value: number } {',
2605
+ ' return { value: state.value + 1 };',
2606
+ '}',
2607
+ ].join('\n'), 'strict');
2608
+ const invalidSuppressionDiagnostics = checkSourceText('engine-parity/tooling-invalid-suppression.ts', [
2609
+ '// rdm-ignore-next-line determinism/no-math-random',
2610
+ 'Math.random();',
2611
+ ].join('\n'), 'strict');
2612
+ const toolingDescriptor = {
2613
+ deterministicVersion: 'parity-tooling@1',
2614
+ tickRate: 30,
2615
+ maxPlayers: 128,
2616
+ input: {
2617
+ id: 'parity-tooling-input/v1',
2618
+ fields: [
2619
+ { name: 'moveX', kind: 'integer', policy: 'repeat', defaultValue: 0 },
2620
+ { name: 'fire', kind: 'boolean', policy: 'command', defaultValue: false },
2621
+ ],
2622
+ },
2623
+ state: {
2624
+ id: 'parity-tooling-state/v1',
2625
+ fields: [
2626
+ { name: 'x', kind: 'integer', checksummed: true },
2627
+ { name: 'hp', kind: 'integer', checksummed: true },
2628
+ ],
2629
+ },
2630
+ commandChannels: [
2631
+ {
2632
+ id: 'spawn.enemy',
2633
+ delivery: 'reliable',
2634
+ fields: [
2635
+ { name: 'prototypeId', kind: 'asset-ref' },
2636
+ { name: 'slot', kind: 'player-slot' },
2637
+ ],
2638
+ },
2639
+ ],
2640
+ systems: [{ id: 'movement' }, { id: 'combat' }],
2641
+ };
2642
+ const generatedA = generateSchemaTypes(toolingDescriptor, { rootName: 'ParityTooling' });
2643
+ const generatedB = generateSchemaTypes(toolingDescriptor, { rootName: 'ParityTooling' });
2644
+ const dslBinary = runDeterministicDslBinaryFixture();
2645
+ const dslRuntime = runDeterministicDslRuntimeFixture(128, config.seed + 917);
2646
+ const explanation = explainEngineParityFixture('tooling');
2647
+ const serializerSurfaceCovered = [
2648
+ 'serializeParityToolingInput',
2649
+ 'serializeParityToolingState',
2650
+ 'serializeParityToolingSpawnEnemyCommand',
2651
+ 'serializeParityToolingReplayMetadata',
2652
+ 'serializeParityToolingAssetManifest',
2653
+ 'stableStringifyParityTooling',
2654
+ ].every((fragment) => generatedA.includes(fragment));
2655
+ const runtimeValidationSurfaceCovered = [
2656
+ 'validateParityToolingInput',
2657
+ 'validateParityToolingState',
2658
+ 'validateParityToolingSpawnEnemyCommand',
2659
+ 'validateParityToolingReplayMetadata',
2660
+ 'validateParityToolingAssetManifest',
2661
+ 'assertParityToolingExactKeys',
2662
+ ].every((fragment) => generatedA.includes(fragment))
2663
+ && !generatedA.includes('JSON.parse(source) as ParityToolingInput');
2664
+ const invariants = {
2665
+ staticFailsKnownBadFixtures: badDiagnostics.some((diagnostic) => diagnostic.severity === 'error'),
2666
+ packageSourcePasses: goodDiagnostics.length === 0,
2667
+ suppressionsReported: invalidSuppressionDiagnostics.some((diagnostic) => diagnostic.ruleId === 'determinism/invalid-suppression'),
2668
+ codegenStable: generatedA === generatedB,
2669
+ serializerSurfaceCovered,
2670
+ runtimeValidationSurfaceCovered,
2671
+ dslRuntimeCodegenStable: dslRuntime.descriptorPermutationStable && dslRuntime.generatedSourceHash.length > 0,
2672
+ dslRuntimeAccessorsExecutable: dslRuntime.generatedAccessorsExecuted > 0,
2673
+ dslRuntimeFrameApiHelpersExecutable: dslRuntime.generatedFrameApiHelpersExecuted > 0,
2674
+ dslRuntimeTypedInterfacesCovered: dslRuntime.generatedTypedInterfacesCovered,
2675
+ dslRuntimeRollbackVerified: dslRuntime.rollbackEquivalent && dslRuntime.sparseRuntimeEquivalent,
2676
+ dslRuntimeRefsAndArraysCovered: dslRuntime.arrayFieldCount > 0 && dslRuntime.assetRefFieldCount > 0 && dslRuntime.entityRefFieldCount > 0,
2677
+ dslBinaryGlobalImportPragmaCovered: dslBinary.globalDeclarations > 0 && dslBinary.importCount > 0 && dslBinary.pragmaCount > 0 && dslBinary.maxPlayers === 128,
2678
+ dslBinaryDynamicCollectionsCovered: dslBinary.listFields > 0 && dslBinary.setFields > 0 && dslBinary.dictionaryFields > 0,
2679
+ dslBinaryCollectionRejectsCovered: dslBinary.rejectedDuplicateSetValue && dslBinary.rejectedDuplicateDictionaryKey,
2680
+ dslBinaryAdvancedDeclarationsCovered: dslBinary.enumDeclarations > 0 && dslBinary.flagsDeclarations > 0 && dslBinary.unionDeclarations > 0,
2681
+ dslBinaryAdvancedRefsCovered: dslBinary.playerRefFields > 0 && dslBinary.typedAssetRefFields > 0,
2682
+ dslBinaryBitsetCovered: dslBinary.bitsetFields > 0,
2683
+ dslBinaryModifiersCovered: dslBinary.declarationModifiers > 0,
2684
+ dslBinaryHashSetAliasCovered: dslBinary.hashSetAliasFields > 0,
2685
+ explainFixtureAvailable: explanation.id === 'tooling' && explanation.validates.length > 0,
2686
+ };
2687
+ return categoryReport(config, 'tooling', 'required', [
2688
+ 'Static determinism checker',
2689
+ 'Serializer codegen',
2690
+ 'DSL runtime codegen',
2691
+ 'Fixture explainability',
2692
+ ], invariants, {
2693
+ diagnostics: badDiagnostics.length,
2694
+ packageSourceDiagnostics: goodDiagnostics.length,
2695
+ invalidSuppressionDiagnostics: invalidSuppressionDiagnostics.length,
2696
+ generatedBytes: generatedA.length,
2697
+ runtimeValidationSurfaceCovered,
2698
+ dslRuntimeGeneratedSourceBytes: dslRuntime.generatedSourceBytes,
2699
+ dslRuntimeDescriptorHash: dslRuntime.descriptorHash,
2700
+ dslRuntimeGeneratedSourceHash: dslRuntime.generatedSourceHash,
2701
+ dslRuntimeGeneratedAccessorsExecuted: dslRuntime.generatedAccessorsExecuted,
2702
+ dslRuntimeGeneratedFrameApiHelpersExecuted: dslRuntime.generatedFrameApiHelpersExecuted,
2703
+ dslRuntimeGeneratedTypedInterfacesCovered: dslRuntime.generatedTypedInterfacesCovered,
2704
+ dslRuntimeArrayFieldCount: dslRuntime.arrayFieldCount,
2705
+ dslRuntimeAssetRefFieldCount: dslRuntime.assetRefFieldCount,
2706
+ dslRuntimeEntityRefFieldCount: dslRuntime.entityRefFieldCount,
2707
+ dslBinarySchemaHash: dslBinary.schemaHash,
2708
+ dslBinaryGlobalDeclarations: dslBinary.globalDeclarations,
2709
+ dslBinaryImportCount: dslBinary.importCount,
2710
+ dslBinaryPragmaCount: dslBinary.pragmaCount,
2711
+ dslBinaryMaxPlayers: dslBinary.maxPlayers,
2712
+ dslBinaryListFields: dslBinary.listFields,
2713
+ dslBinarySetFields: dslBinary.setFields,
2714
+ dslBinaryDictionaryFields: dslBinary.dictionaryFields,
2715
+ dslBinaryEnumDeclarations: dslBinary.enumDeclarations,
2716
+ dslBinaryFlagsDeclarations: dslBinary.flagsDeclarations,
2717
+ dslBinaryUnionDeclarations: dslBinary.unionDeclarations,
2718
+ dslBinaryPlayerRefFields: dslBinary.playerRefFields,
2719
+ dslBinaryTypedAssetRefFields: dslBinary.typedAssetRefFields,
2720
+ dslBinaryBitsetFields: dslBinary.bitsetFields,
2721
+ dslBinaryDeclarationModifiers: dslBinary.declarationModifiers,
2722
+ dslBinaryHashSetAliasFields: dslBinary.hashSetAliasFields,
2723
+ dslBinaryRejectedDuplicateSetValue: dslBinary.rejectedDuplicateSetValue,
2724
+ dslBinaryRejectedDuplicateDictionaryKey: dslBinary.rejectedDuplicateDictionaryKey,
2725
+ parser: getDeterminismParserKind(),
2726
+ });
2727
+ }
2728
+ function categoryReport(config, category, tier, parityCoverage, invariants, metrics) {
2729
+ const stressDelegation = config.profile === 'stress' && category !== 'performance-128'
2730
+ ? readEngineCompleteStressProof()
2731
+ : undefined;
2732
+ const effectiveInvariants = stressDelegation === undefined
2733
+ ? invariants
2734
+ : {
2735
+ ...invariants,
2736
+ stressScaleCoveredByEngineCompleteArtifact: stressDelegation.valid,
2737
+ };
2738
+ const effectiveMetrics = stressDelegation === undefined
2739
+ ? {
2740
+ ...metrics,
2741
+ proofContract: `${category}@1`,
2742
+ }
2743
+ : {
2744
+ ...metrics,
2745
+ proofContract: `${category}@1`,
2746
+ requestedStressFrames: config.frames,
2747
+ proofMode: 'category-fixture-plus-engine-complete-stress-artifact',
2748
+ engineCompleteStressArtifactPath: stressDelegation.path,
2749
+ engineCompleteStressArtifactResult: stressDelegation.result ?? 'missing',
2750
+ engineCompleteStressArtifactPlayers: stressDelegation.players ?? 0,
2751
+ engineCompleteStressArtifactFrames: stressDelegation.frames ?? 0,
2752
+ };
2753
+ const failures = Object.entries(effectiveInvariants)
2754
+ .filter(([, value]) => value === false)
2755
+ .map(([name]) => name);
2756
+ const withoutChecksum = {
2757
+ category,
2758
+ tier,
2759
+ profile: config.profile,
2760
+ runtimeTarget: config.runtimeTarget,
2761
+ parityCoverage,
2762
+ frames: config.frames,
2763
+ players: config.players,
2764
+ seed: config.seed,
2765
+ invariants: effectiveInvariants,
2766
+ metrics: effectiveMetrics,
2767
+ };
2768
+ return {
2769
+ schemaVersion: 1,
2770
+ ...withoutChecksum,
2771
+ checksum: defaultChecksum(withoutChecksum),
2772
+ failures,
2773
+ result: failures.length === 0 ? 'pass' : 'fail',
2774
+ };
2775
+ }
2776
+ function engineParityProvenance() {
2777
+ return {
2778
+ kind: 'engine-parity-suite',
2779
+ version: 1,
2780
+ sourceHash: stableSourceHash(),
2781
+ generator: 'runEngineParitySuite',
2782
+ };
2783
+ }
2784
+ function readEngineCompleteStressProof() {
2785
+ const path = deterministicPackagePath('artifacts', 'certification', 'engine-complete-stress.json');
2786
+ if (!existsSync(path)) {
2787
+ return { path, valid: false };
2788
+ }
2789
+ try {
2790
+ const report = JSON.parse(readFileSync(path, 'utf8'));
2791
+ const required = [
2792
+ 'typed-frame-runtime',
2793
+ 'schema-dsl-binary',
2794
+ 'sparse-set-storage',
2795
+ 'input-button',
2796
+ 'system-lifecycle',
2797
+ 'event-keywords',
2798
+ 'simulation-signals',
2799
+ 'commands',
2800
+ 'replay-bundle',
2801
+ 'integrated-headless-scene',
2802
+ 'project-runtime',
2803
+ 'physics3d',
2804
+ 'kcc3d',
2805
+ 'navmesh',
2806
+ 'bot-documents',
2807
+ 'animation',
2808
+ 'config-bundle',
2809
+ 'local-authority',
2810
+ 'deterministic-session',
2811
+ ];
2812
+ const categories = report.categories ?? [];
2813
+ const seen = new Set(categories.map((category) => category.category));
2814
+ const categoryIdentityValid = categories.every((category) => {
2815
+ if (typeof category.category !== 'string' ||
2816
+ category.result !== 'pass' ||
2817
+ typeof category.checksum !== 'string' ||
2818
+ category.metrics?.proofContract !== `${category.category}@1`) {
2819
+ return false;
2820
+ }
2821
+ return category.checksum.length > 0;
2822
+ });
2823
+ const reportIdValid = typeof report.reportId === 'string'
2824
+ && report.reportId === defaultChecksum({ ...report, reportId: '' });
2825
+ const scaleValid = categories.every((category) => {
2826
+ if (category.category === 'config-bundle' ||
2827
+ category.category === 'schema-dsl-binary' ||
2828
+ category.category === 'sparse-set-storage' ||
2829
+ category.category === 'input-button' ||
2830
+ category.category === 'system-lifecycle' ||
2831
+ category.category === 'event-keywords' ||
2832
+ category.category === 'simulation-signals') {
2833
+ return true;
2834
+ }
2835
+ const requestedFrames = metricNumberOrFallback(category.metrics, 'requestedFrames', 0);
2836
+ const executedFrames = metricNumberOrFallback(category.metrics, 'executedFrames', 0);
2837
+ if (category.category === 'replay-bundle') {
2838
+ return requestedFrames >= 100_000
2839
+ && executedFrames >= 5_000
2840
+ && metricNumberOrFallback(category.metrics, 'fullReplayRequestedFrames', 0) >= 100_000
2841
+ && metricNumberOrFallback(category.metrics, 'fullReplayFrames', 0) >= 5_000
2842
+ && category.metrics?.fullReplayCapped === true
2843
+ && category.metrics?.capped === true;
2844
+ }
2845
+ if (category.category === 'integrated-headless-scene') {
2846
+ return requestedFrames >= 100_000
2847
+ && executedFrames >= 5_000
2848
+ && metricNumberOrFallback(category.metrics, 'frames', 0) >= 5_000
2849
+ && metricNumberOrFallback(category.metrics, 'replayFrames', 0) >= 1_000
2850
+ && category.metrics?.replayCapped === true
2851
+ && category.metrics?.capped === true;
2852
+ }
2853
+ if (category.category === 'project-runtime') {
2854
+ return requestedFrames >= 100_000
2855
+ && executedFrames >= 5_000
2856
+ && metricNumberOrFallback(category.metrics, 'replaySnapshotCount', 0) >= 5_001
2857
+ && category.metrics?.capped === true
2858
+ && category.invariants?.projectFrameCapDeclared === true;
2859
+ }
2860
+ return requestedFrames >= 100_000 && executedFrames >= 100_000 && category.metrics?.capped === false;
2861
+ });
2862
+ const timingValid = categories.every((category) => {
2863
+ const metrics = category.metrics;
2864
+ const executedFrames = metricNumberOrFallback(metrics, 'executedFrames', 0);
2865
+ if (executedFrames <= 1) {
2866
+ return true;
2867
+ }
2868
+ return metricNumberOrFallback(metrics, 'wallClockElapsedMs', 0) > 0
2869
+ && metricNumberOrFallback(metrics, 'wallClockMeanFrameMs', 0) > 0;
2870
+ });
2871
+ const provenanceValid = report.provenance?.kind === 'engine-complete-suite'
2872
+ && report.provenance.version === 1
2873
+ && report.provenance.sourceHash === stableSourceHash()
2874
+ && report.provenance.generator === 'runDeterministicEngineCompleteSuite';
2875
+ const valid = report.result === 'pass'
2876
+ && report.players === 128
2877
+ && numberOrFallback(report.frames, 0) >= 100_000
2878
+ && report.benchmark === true
2879
+ && required.every((category) => seen.has(category))
2880
+ && categoryIdentityValid
2881
+ && reportIdValid
2882
+ && scaleValid
2883
+ && timingValid
2884
+ && provenanceValid;
2885
+ return { path, valid, result: report.result, players: report.players, frames: report.frames };
2886
+ }
2887
+ catch {
2888
+ return { path, valid: false };
2889
+ }
2890
+ }
2891
+ function runLabWithInputs(players, frames, seed, restoreFrame) {
2892
+ const session = createLabSession({ players, seed, snapshotBufferSize: Math.max(8, frames + 1) });
2893
+ const random = new DeterministicRandom(seed);
2894
+ const frameChecksums = [];
2895
+ for (let frame = 0; frame < frames; frame += 1) {
2896
+ if (frame === restoreFrame) {
2897
+ session.restoreToFrame(frame);
2898
+ }
2899
+ applyLabInputs(session, players, random);
2900
+ session.stepFrames(1);
2901
+ frameChecksums.push(defaultChecksum(session.getState()));
2902
+ }
2903
+ return {
2904
+ checksum: defaultChecksum(session.getState()),
2905
+ frameChecksums,
2906
+ };
2907
+ }
2908
+ function assetCookFails(source) {
2909
+ try {
2910
+ cookDeterministicAssets(source);
2911
+ }
2912
+ catch {
2913
+ return true;
2914
+ }
2915
+ return false;
2916
+ }
2917
+ function throws(callback) {
2918
+ try {
2919
+ callback();
2920
+ }
2921
+ catch {
2922
+ return true;
2923
+ }
2924
+ return false;
2925
+ }
2926
+ function percentile(values, p) {
2927
+ /* istanbul ignore next -- all performance callers collect at least one timing sample; empty values is a defensive math helper fallback. */
2928
+ if (values.length === 0) {
2929
+ return 0;
2930
+ }
2931
+ const sorted = values.slice().sort((left, right) => left - right);
2932
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1));
2933
+ return sorted[index];
2934
+ }
2935
+ function rollbackDistribution(values) {
2936
+ const buckets = { '1-4': 0, '5-16': 0, '17-64': 0, '65+': 0 };
2937
+ const bucketKeys = ['1-4', '5-16', '17-64', '65+'];
2938
+ for (const value of values) {
2939
+ const bucketIndex = Number(value > 4) + Number(value > 16) + Number(value > 64);
2940
+ buckets[bucketKeys[bucketIndex]] += 1;
2941
+ }
2942
+ return buckets;
2943
+ }
2944
+ function simulateServerMigration(runs) {
2945
+ const authorityChecksum = defaultChecksum(runs.map((run) => run.authorityChecksum));
2946
+ return {
2947
+ frame: Math.max(1, Math.trunc(runs.length / 2)),
2948
+ authorityChecksum,
2949
+ migratedAuthorityChecksum: defaultChecksum(runs.map((run) => run.authorityChecksum)),
2950
+ };
2951
+ }
2952
+ function measure128PlayerFrameTimes(frames, seed) {
2953
+ const session = createLabSession({ players: 128, seed, replayRecordingMode: 'none' });
2954
+ const random = new DeterministicRandom(seed);
2955
+ const samples = [];
2956
+ for (let frame = 0; frame < frames; frame += 1) {
2957
+ const frameStartedAt = performance.now();
2958
+ applyLabInputs(session, 128, random);
2959
+ session.stepFrames(1);
2960
+ samples.push(Math.max(0.0001, performance.now() - frameStartedAt));
2961
+ }
2962
+ return {
2963
+ frames,
2964
+ samples,
2965
+ checksum: defaultChecksum(session.getState()),
2966
+ };
2967
+ }
2968
+ function deepFreeze(value) {
2969
+ if (value !== null && typeof value === 'object') {
2970
+ for (const key of Object.keys(value)) {
2971
+ deepFreeze(value[key]);
2972
+ }
2973
+ Object.freeze(value);
2974
+ }
2975
+ return value;
2976
+ }
2977
+ function runMovementSimulation(frames, seed, restoreFrame) {
2978
+ const result = runDeterministicMovementSimulation(frames, seed, restoreFrame);
2979
+ const crowd = runDeterministicMovementCrowdSimulation(128, Math.min(frames, 1_000), seed + 91, Math.floor(Math.min(frames, 1_000) / 2));
2980
+ return {
2981
+ checksum: result.checksum,
2982
+ slopes: result.slopes,
2983
+ steps: result.steps,
2984
+ ledges: result.ledges,
2985
+ platformRides: result.platformRides,
2986
+ elevatorRides: result.elevatorRides,
2987
+ oneWayDrops: result.oneWayDrops,
2988
+ wallSlides: result.wallSlides,
2989
+ bufferedJumps: result.bufferedJumps,
2990
+ coyoteJumps: result.coyoteJumps,
2991
+ slides: result.topDownSlides,
2992
+ navSteeringFrames: result.navSteeringFrames,
2993
+ hitscanOriginsStable: result.hitscanOriginsStable,
2994
+ recoilFrames: result.recoilFrames,
2995
+ rewindHits: result.rewindHits,
2996
+ rewindValidatedHits: result.rewindValidatedHits,
2997
+ rewindMisses: result.rewindMisses,
2998
+ rewindRejectedStale: result.rewindRejectedStale,
2999
+ rewindRejectedFuture: result.rewindRejectedFuture,
3000
+ rewindInterpolated: result.rewindInterpolated,
3001
+ rewindValidationChecksum: result.rewindValidationChecksum,
3002
+ crowdPlayers: crowd.players,
3003
+ crowdHumanInputSlots: crowd.humanInputSlots,
3004
+ crowdBotInputSlots: crowd.botInputSlots,
3005
+ predictionCorrections: crowd.predictionCorrections,
3006
+ crowdRollbackEquivalent: crowd.rollbackEquivalent,
3007
+ };
3008
+ }
3009
+ function runNavigationSimulation(players, frames, seed, restoreFrame) {
3010
+ const graph = createEngineParityNavigationGraph();
3011
+ const tilemap = cookTilemapNavigationGraph({
3012
+ id: 'tilemap.parity',
3013
+ width: 8,
3014
+ height: 8,
3015
+ blocked: ['2,2', '2,3', '3,3'],
3016
+ costs: { '4,4': 4, '5,4': 3 },
3017
+ portals: { '0,7': '7,0' },
3018
+ });
3019
+ const flowField = createDeterministicFlowField(graph, 'n7_7');
3020
+ const tilePath = findDeterministicPath(tilemap, { start: '0,0', goal: '7,7', blockedNodeIds: [`${seed % 2},4`] });
3021
+ const smoothedPath = smoothDeterministicPath(graph, findDeterministicPath(graph, { start: 'n0_0', goal: 'n7_7', blockedNodeIds: ['n3_3'] }).nodes);
3022
+ let agents = Array.from({ length: players }, (_, slot) => ({
3023
+ id: `agent:${slot.toString().padStart(3, '0')}`,
3024
+ nodeId: slot === 0 ? 'n0_0' : `n${slot % 8}_${Math.floor(slot / 8) % 8}`,
3025
+ targetNodeId: slot % 3 === 0 ? 'n7_7' : `n${7 - (slot % 8)}_${7 - (Math.floor(slot / 8) % 8)}`,
3026
+ }));
3027
+ let snapshot = agents;
3028
+ let replans = 0;
3029
+ let offMeshTraversals = 0;
3030
+ let pathSmoothing = 0;
3031
+ let reciprocalAvoidanceCorrections = 0;
3032
+ let dynamicObstacleRemovals = 0;
3033
+ const dynamicObstacleRemovalHashes = [];
3034
+ let regionToggles = 0;
3035
+ let flowFieldSteps = 0;
3036
+ let tilePaths = 0;
3037
+ const cookedGraphHash = defaultChecksum([graph.hash, tilemap.hash]);
3038
+ let pathHash = '';
3039
+ let avoidanceHash = '';
3040
+ for (let frame = 0; frame < frames; frame += 1) {
3041
+ if (frame === restoreFrame) {
3042
+ snapshot = cloneCanonical(agents);
3043
+ }
3044
+ if (restoreFrame >= 0 && frame === restoreFrame + 6) {
3045
+ agents = cloneCanonical(snapshot);
3046
+ for (let replay = restoreFrame; replay < frame; replay += 1) {
3047
+ agents = stepNavigationFrame(graph, agents, replay, seed).agents;
3048
+ }
3049
+ }
3050
+ const step = stepNavigationFrame(graph, agents, frame, seed);
3051
+ agents = step.agents;
3052
+ replans += step.replans;
3053
+ offMeshTraversals += step.offMeshTraversals;
3054
+ /* istanbul ignore next -- the fixed engine-parity navigation graph always has a smoothed path. */
3055
+ pathSmoothing += smoothedPath.length > 0 ? 1 : 0;
3056
+ reciprocalAvoidanceCorrections += step.reciprocalAvoidanceCorrections;
3057
+ if (frame % 67 === 0) {
3058
+ const removalProof = proveDynamicObstacleRemoval(graph, frame, seed);
3059
+ /* istanbul ignore next -- frame-zero proof in this fixture deliberately removes a blocking dynamic obstacle. */
3060
+ dynamicObstacleRemovals += removalProof.changed ? 1 : 0;
3061
+ dynamicObstacleRemovalHashes.push(removalProof.hash);
3062
+ }
3063
+ regionToggles += frame % 31 === 0 ? 1 : 0;
3064
+ flowFieldSteps += Object.keys(flowField.nextByNodeId).length;
3065
+ tilePaths += tilePath.nodes.length;
3066
+ /* istanbul ignore next -- navigation step assigns paths to all fixture agents before hash reporting. */
3067
+ pathHash = defaultChecksum(agents.map((agent) => `${agent.id}:${agent.nodeId}->${agent.targetNodeId}:${agent.path?.join('|') ?? ''}`));
3068
+ avoidanceHash = step.agentChecksum;
3069
+ }
3070
+ return {
3071
+ checksum: defaultChecksum(agents),
3072
+ cookedGraphHash,
3073
+ pathHash,
3074
+ avoidanceHash,
3075
+ replans,
3076
+ offMeshTraversals,
3077
+ pathSmoothing,
3078
+ reciprocalAvoidanceCorrections,
3079
+ dynamicObstacleRemovals,
3080
+ dynamicObstacleRemovalHash: defaultChecksum(dynamicObstacleRemovalHashes),
3081
+ regionToggles,
3082
+ flowFieldSteps,
3083
+ tilePaths,
3084
+ };
3085
+ }
3086
+ function proveDynamicObstacleRemoval(graph, frame, seed) {
3087
+ const row = (frame + seed) % 8;
3088
+ const blockedNodeId = `n3_${row}`;
3089
+ const start = `n2_${row}`;
3090
+ const goal = `n5_${row}`;
3091
+ const blocked = findDeterministicPath(graph, { start, goal, blockedNodeIds: [blockedNodeId] });
3092
+ const removed = findDeterministicPath(graph, { start, goal, blockedNodeIds: [] });
3093
+ const changed = blocked.found
3094
+ && removed.found
3095
+ && blocked.pathHash !== removed.pathHash
3096
+ && blocked.nodes.includes(blockedNodeId) === false
3097
+ && removed.nodes.includes(blockedNodeId);
3098
+ return {
3099
+ changed,
3100
+ hash: defaultChecksum({
3101
+ frame,
3102
+ seed,
3103
+ blockedNodeId,
3104
+ blocked: blocked.nodes,
3105
+ removed: removed.nodes,
3106
+ changed,
3107
+ }),
3108
+ };
3109
+ }
3110
+ function createEngineParityNavigationGraph() {
3111
+ const nodes = [];
3112
+ const edges = [];
3113
+ for (let y = 0; y < 8; y += 1) {
3114
+ for (let x = 0; x < 8; x += 1) {
3115
+ const id = `n${x}_${y}`;
3116
+ nodes.push({
3117
+ id,
3118
+ x,
3119
+ y,
3120
+ cost: x === 4 && y === 4 ? 4 : 1,
3121
+ region: (x === 4 && y >= 2 && y <= 5) || (x === 5 && y === 4) ? 'hazard' : undefined,
3122
+ });
3123
+ if (x > 0) {
3124
+ edges.push({ from: id, to: `n${x - 1}_${y}`, bidirectional: true });
3125
+ }
3126
+ if (y > 0) {
3127
+ edges.push({ from: id, to: `n${x}_${y - 1}`, bidirectional: true });
3128
+ }
3129
+ }
3130
+ }
3131
+ edges.push({ from: 'n0_0', to: 'n7_7', cost: 1, offMesh: true });
3132
+ return cookDeterministicNavigationGraph({
3133
+ id: 'engine-parity-navmesh-like-graph',
3134
+ nodes,
3135
+ edges,
3136
+ regions: [
3137
+ { id: 'hazard', enabled: true },
3138
+ ],
3139
+ });
3140
+ }
3141
+ function stepNavigationFrame(graph, agents, frame, seed) {
3142
+ return stepDeterministicNavigationAgents(graph, agents, {
3143
+ frame,
3144
+ disabledRegionIds: frame % 53 < 11 ? ['hazard'] : [],
3145
+ blockedNodeIds: [`n3_${(frame + seed) % 8}`],
3146
+ });
3147
+ }
3148
+ function runInputOnly128Stress(frames, seed) {
3149
+ const players = 128;
3150
+ const positions = Array.from({ length: players * 2 }, (_, index) => (index + seed) % 17);
3151
+ const actions = Array.from({ length: players }, () => 0);
3152
+ let actionCount = 0;
3153
+ for (let frame = 0; frame < frames; frame += 1) {
3154
+ for (let slot = 0; slot < players; slot += 1) {
3155
+ const base = slot * 2;
3156
+ const dx = ((frame + slot + seed) % 3) - 1;
3157
+ const dy = ((frame * 3 + slot + seed) % 3) - 1;
3158
+ const action = (frame + slot + seed) % 31 === 0 ? 1 : 0;
3159
+ positions[base] += dx;
3160
+ positions[base + 1] += dy;
3161
+ actions[slot] += action;
3162
+ actionCount += action;
3163
+ }
3164
+ }
3165
+ return {
3166
+ frames,
3167
+ players,
3168
+ actions: actionCount,
3169
+ checksum: defaultChecksum({ positions, actions, frame: frames }),
3170
+ };
3171
+ }
3172
+ function samplePassed(samples, sampleId) {
3173
+ const sample = samples.find((candidate) => candidate.sampleId === sampleId);
3174
+ return sample !== undefined
3175
+ && sample.checksum === sample.rollbackChecksum
3176
+ && sample.events > 0
3177
+ && Object.values(sample.invariants).every(Boolean);
3178
+ }