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