@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,745 @@
1
+ import { cloneCanonical, defaultChecksum } from './canonical.js';
2
+ import { cookDeterministicAnimation, createDeterministicAnimationRuntimeState, stepDeterministicAnimation } from './animation.js';
3
+ import { compileDeterministicBotDocument, traceDeterministicBotDocument } from './bot-documents.js';
4
+ import { createDeterministicCommandTimeline } from './commands.js';
5
+ import { cookDeterministicConfigBundle } from './config-bundle.js';
6
+ import { createDeterministicFrameRuntime } from './engine-runtime.js';
7
+ import { stepDeterministicKcc3D } from './movement3d.js';
8
+ import { cookDeterministicNavmesh, stepDeterministicNavmeshAgents } from './navmesh.js';
9
+ import { createDeterministicPhysicsWorld3D, stepDeterministicPhysicsWorld3D } from './physics3d.js';
10
+ import { createDeterministicReplayBundle, verifyDeterministicFrameRuntimeReplayBundle } from './replay-bundle.js';
11
+ export const deterministicSampleSceneIds = [
12
+ 'asteroids',
13
+ 'simple-fps',
14
+ 'twin-stick',
15
+ 'platform-shooter',
16
+ 'arena-brawler',
17
+ 'vehicle',
18
+ 'mini-golf',
19
+ 'waiting-room',
20
+ 'boat-water',
21
+ 'animator',
22
+ ];
23
+ export function runDeterministicSampleScene(sampleId, frames, seed) {
24
+ const control = stepSampleScene(sampleId, frames, seed, -1);
25
+ const restored = stepSampleScene(sampleId, frames, seed, Math.floor(frames / 2));
26
+ const subsystemMetrics = sampleSubsystemProofMetrics(sampleId, Math.min(frames, 48), seed);
27
+ const metrics = {
28
+ ...restored.metrics,
29
+ ...subsystemMetrics,
30
+ };
31
+ return {
32
+ sampleId,
33
+ checksum: defaultChecksum({ sampleId, baseChecksum: control.checksum, subsystemMetrics }),
34
+ rollbackChecksum: defaultChecksum({ sampleId, baseChecksum: restored.checksum, subsystemMetrics }),
35
+ events: restored.events,
36
+ invariants: sampleInvariants(sampleId, metrics, control.checksum === restored.checksum),
37
+ metrics,
38
+ };
39
+ }
40
+ export function runAllDeterministicSampleScenes(frames, seed) {
41
+ return deterministicSampleSceneIds.map((sampleId, index) => runDeterministicSampleScene(sampleId, frames, seed + index));
42
+ }
43
+ export function createDeterministicSampleSceneCertificationTrace(frames, players, seed) {
44
+ const samples = deterministicSampleSceneIds.map((sampleId, index) => createDeterministicSampleSceneTrace(sampleId, frames, seed + index));
45
+ const integratedProof = runDeterministicIntegratedHeadlessScene(Math.min(frames, 1_000), Math.min(players, 128), seed + 991);
46
+ const integrated = {
47
+ checksum: integratedProof.checksum,
48
+ replayChecksum: integratedProof.replayChecksum,
49
+ rollbackChecksum: integratedProof.rollbackChecksum,
50
+ replayVerified: integratedProof.replayVerified,
51
+ rollbackEquivalent: integratedProof.rollbackEquivalent,
52
+ replaySnapshots: integratedProof.metrics.replaySnapshots,
53
+ commandsAccepted: integratedProof.metrics.commandsAccepted,
54
+ physicsContacts: integratedProof.metrics.physicsContacts,
55
+ kccCorrections: integratedProof.metrics.kccCorrections,
56
+ navReplans: integratedProof.metrics.navReplans,
57
+ botTraceRows: integratedProof.metrics.botTraceRows,
58
+ animationEvents: integratedProof.metrics.animationEvents,
59
+ };
60
+ const withoutHash = {
61
+ schemaVersion: 1,
62
+ kind: 'deterministic-sample-scene-certification-trace',
63
+ frames,
64
+ players,
65
+ seed,
66
+ samples,
67
+ integrated,
68
+ sampleCount: samples.length,
69
+ mechanicCount: samples.reduce((sum, sample) => sum + sample.mechanicCount, 0),
70
+ rollbackEquivalent: samples.every((sample) => sample.rollbackEquivalent) && integrated.rollbackEquivalent,
71
+ };
72
+ return {
73
+ ...withoutHash,
74
+ traceHash: defaultChecksum(withoutHash),
75
+ };
76
+ }
77
+ export function runDeterministicIntegratedHeadlessScene(frames, players, seed) {
78
+ const descriptor = integratedRuntimeDescriptor();
79
+ const systems = integratedRuntimeSystems();
80
+ const config = cookDeterministicConfigBundle({
81
+ version: 1,
82
+ engineHash: 'integrated-engine@1',
83
+ schemaHash: defaultChecksum(descriptor),
84
+ sessionConfig: { players, tickRate: 60 },
85
+ simulationConfig: { numericMode: 'fixed', rollback: true },
86
+ runtimeConfig: { maxRollbackFrames: 32 },
87
+ playerConfig: { team: 'blue' },
88
+ assets: {
89
+ schemaVersion: 1,
90
+ engineVersion: 'integrated-engine@1',
91
+ assets: [
92
+ { id: 'ability.dash', type: 'ability', references: ['player.prototype'] },
93
+ { id: 'player.prototype', type: 'prototype', prototype: true, components: { hp: 100, speed: 4 } },
94
+ ],
95
+ },
96
+ });
97
+ const navmesh = cookDeterministicNavmesh({
98
+ id: 'integrated-arena',
99
+ polygons: [
100
+ { id: 'a', vertices: [{ x: 0, y: 0 }, { x: 10, y: 0 }, { x: 10, y: 10 }, { x: 0, y: 10 }] },
101
+ { id: 'b', vertices: [{ x: 10, y: 0 }, { x: 20, y: 0 }, { x: 20, y: 10 }, { x: 10, y: 10 }] },
102
+ ],
103
+ links: [{ from: 'a', to: 'b', bidirectional: true }],
104
+ });
105
+ const botDocument = {
106
+ id: 'integrated-bot',
107
+ version: 1,
108
+ blackboard: [{ key: 'enemyNear', value: true }, { key: 'threat', value: 2 }],
109
+ assetRefs: ['ability.dash'],
110
+ behaviorTree: { id: 'root', type: 'selector', children: [{ id: 'dash', type: 'action', action: 'dash' }] },
111
+ utility: [{ id: 'dash-utility', inputKey: 'threat', action: 'dash', min: 0, max: 4, weight: 5 }],
112
+ };
113
+ const compiledBot = compileDeterministicBotDocument(botDocument, ['ability.dash']);
114
+ const botTrace = traceDeterministicBotDocument(botDocument, [0, 1], Math.max(1, frames), ['ability.dash']);
115
+ const animationDescriptor = integratedAnimationDescriptor();
116
+ const animation = cookDeterministicAnimation(animationDescriptor);
117
+ const commandTimeline = createDeterministicCommandTimeline({
118
+ playerCount: players,
119
+ currentFrame: 0,
120
+ channels: [{ id: 'ability', reliable: true, allowPrediction: true, maxPayloadBytes: 128, maxFrameLead: frames + 1 }],
121
+ validAssetRefs: ['ability.dash'],
122
+ });
123
+ recordIntegratedCommands(commandTimeline, frames, players);
124
+ const replayFrames = Math.min(frames, 1_000);
125
+ const control = stepIntegratedScene({
126
+ descriptor,
127
+ systems,
128
+ frames,
129
+ seed,
130
+ commandTimeline,
131
+ navmesh,
132
+ botTrace,
133
+ animationDescriptor,
134
+ configHash: config.configHash,
135
+ rollbackFrame: -1,
136
+ replayFrameLimit: replayFrames,
137
+ });
138
+ const restored = stepIntegratedScene({
139
+ descriptor,
140
+ systems,
141
+ frames,
142
+ seed,
143
+ commandTimeline,
144
+ navmesh,
145
+ botTrace,
146
+ animationDescriptor,
147
+ configHash: config.configHash,
148
+ rollbackFrame: Math.floor(frames / 2),
149
+ replayFrameLimit: -1,
150
+ });
151
+ const bundle = createDeterministicReplayBundle({
152
+ runtimeVersion: 'integrated-headless@1',
153
+ engineHash: 'integrated-engine@1',
154
+ schemaHash: defaultChecksum(descriptor),
155
+ assetDbHash: config.assetDbHash,
156
+ configHash: config.configHash,
157
+ sampleId: 'integrated-headless',
158
+ inputHistory: control.inputHistory.filter((input) => input.frame < replayFrames),
159
+ commandHistory: control.commandHistory.filter((command) => command.frame < replayFrames),
160
+ checksums: control.snapshots
161
+ .filter((snapshot) => snapshot.frame <= replayFrames)
162
+ .map((snapshot) => ({ frame: snapshot.frame, checksum: snapshot.checksum })),
163
+ debugEvents: control.runtimeDebugEvents.filter((event) => event.frame <= replayFrames),
164
+ snapshots: control.snapshots.filter((snapshot) => snapshot.frame <= replayFrames),
165
+ metadata: {
166
+ players,
167
+ frames,
168
+ replayFrames,
169
+ replayCapped: replayFrames < frames,
170
+ botHash: compiledBot.hash,
171
+ animationHash: animation.hash,
172
+ navmeshHash: navmesh.hash,
173
+ },
174
+ });
175
+ const replay = verifyDeterministicFrameRuntimeReplayBundle(bundle, {
176
+ descriptor,
177
+ systems,
178
+ seed,
179
+ beforeFrame: injectIntegratedRuntimeFrame,
180
+ });
181
+ const metrics = {
182
+ frames,
183
+ replayFrames,
184
+ replayCapped: replayFrames < frames,
185
+ players,
186
+ commandsAccepted: commandTimeline.accepted.length,
187
+ sameFrameCommandsAccepted: commandTimeline.poll(0, 0, 'ability').length,
188
+ physicsContacts: control.physicsContacts,
189
+ physicsQueries: control.physicsQueries,
190
+ kccCorrections: control.kccCorrections,
191
+ navReplans: control.navReplans,
192
+ botTraceRows: botTrace.length,
193
+ animationEvents: control.animationEvents,
194
+ replaySnapshots: bundle.snapshots.length,
195
+ debugEvents: bundle.debugEvents.length,
196
+ configHashCovered: config.configHash.length > 0,
197
+ };
198
+ const invariants = {
199
+ frameRuntimeExecuted: replay.frames === replayFrames + 1,
200
+ replayVerified: replay.result === 'pass',
201
+ rollbackEquivalent: control.checksum === restored.checksum,
202
+ commandsExecuted: metrics.commandsAccepted > frames && metrics.sameFrameCommandsAccepted >= 2,
203
+ physicsExecuted: metrics.physicsQueries >= frames && metrics.physicsContacts > 0,
204
+ kccExecuted: metrics.kccCorrections > 0,
205
+ navmeshExecuted: metrics.navReplans > 0,
206
+ botExecuted: metrics.botTraceRows >= frames,
207
+ animationExecuted: metrics.animationEvents > 0,
208
+ assetsConfigCovered: metrics.configHashCovered && config.assetDbHash.length > 0,
209
+ };
210
+ return {
211
+ checksum: control.checksum,
212
+ replayChecksum: replay.finalChecksum,
213
+ rollbackChecksum: restored.checksum,
214
+ replayVerified: replay.result === 'pass',
215
+ rollbackEquivalent: control.checksum === restored.checksum,
216
+ metrics,
217
+ invariants: Object.fromEntries(Object.entries(invariants).sort((left, right) => compareStrings(left[0], right[0]))),
218
+ };
219
+ }
220
+ function stepSampleScene(sampleId, frames, seed, restoreFrame) {
221
+ let state = { frame: 0, x: 0, y: 0, hp: 10, score: 0, phase: 0, cooldown: 0 };
222
+ let snapshot = state;
223
+ let metricsSnapshot = {};
224
+ let events = 0;
225
+ let metrics = {};
226
+ for (let frame = 0; frame < frames; frame += 1) {
227
+ if (frame === restoreFrame) {
228
+ snapshot = cloneCanonical(state);
229
+ metricsSnapshot = cloneCanonical(metrics);
230
+ }
231
+ if (restoreFrame >= 0 && frame === restoreFrame + 4) {
232
+ state = cloneCanonical(snapshot);
233
+ metrics = cloneCanonical(metricsSnapshot);
234
+ for (let replay = restoreFrame; replay < frame; replay += 1) {
235
+ state = advanceSampleState(sampleId, state, replay, seed, metrics);
236
+ }
237
+ }
238
+ state = advanceSampleState(sampleId, state, frame, seed, metrics);
239
+ events += 1 + ((frame + seed + sampleId.length) % 19 === 0 ? 1 : 0);
240
+ }
241
+ return {
242
+ checksum: defaultChecksum({ sampleId, state, metrics }),
243
+ events,
244
+ metrics: Object.fromEntries(Object.entries(metrics).sort((left, right) => compareStrings(left[0], right[0]))),
245
+ };
246
+ }
247
+ function createDeterministicSampleSceneTrace(sampleId, frames, seed) {
248
+ const result = runDeterministicSampleScene(sampleId, frames, seed);
249
+ const representativeStates = traceSampleSceneStates(sampleId, frames, seed);
250
+ const mechanics = requiredMetricKeys(sampleId).filter((mechanic) => result.invariants[mechanic] === true);
251
+ const withoutHash = {
252
+ sampleId,
253
+ frames,
254
+ checksum: result.checksum,
255
+ rollbackChecksum: result.rollbackChecksum,
256
+ rollbackEquivalent: result.invariants.rollbackEquivalent === true && result.checksum === result.rollbackChecksum,
257
+ mechanics,
258
+ mechanicCount: mechanics.length,
259
+ events: result.events,
260
+ representativeStates,
261
+ };
262
+ return {
263
+ ...withoutHash,
264
+ traceHash: defaultChecksum(withoutHash),
265
+ };
266
+ }
267
+ function traceSampleSceneStates(sampleId, frames, seed) {
268
+ const sampleFrames = [...new Set([
269
+ 0,
270
+ Math.max(0, Math.floor(frames / 2)),
271
+ Math.max(0, frames - 1),
272
+ ])].sort((left, right) => left - right);
273
+ let state = { frame: 0, x: 0, y: 0, hp: 10, score: 0, phase: 0, cooldown: 0 };
274
+ const metrics = {};
275
+ const states = [];
276
+ for (let frame = 0; frame < frames; frame += 1) {
277
+ state = advanceSampleState(sampleId, state, frame, seed, metrics);
278
+ if (sampleFrames.includes(frame)) {
279
+ states.push({
280
+ frame: state.frame,
281
+ stateChecksum: defaultChecksum({ sampleId, state, metrics }),
282
+ x: state.x,
283
+ y: state.y,
284
+ hp: state.hp,
285
+ score: state.score,
286
+ phase: state.phase,
287
+ });
288
+ }
289
+ }
290
+ return states;
291
+ }
292
+ function sampleSubsystemProofMetrics(sampleId, frames, seed) {
293
+ if (sampleId !== 'simple-fps' && sampleId !== 'platform-shooter' && sampleId !== 'arena-brawler') {
294
+ return {};
295
+ }
296
+ const proof = runDeterministicIntegratedHeadlessScene(Math.max(16, frames), 16, seed + sampleId.length);
297
+ return {
298
+ subsystemFrameRuntimeFrames: proof.metrics.frames,
299
+ subsystemCommandsAccepted: proof.metrics.commandsAccepted,
300
+ subsystemPhysicsContacts: proof.metrics.physicsContacts,
301
+ subsystemPhysicsQueries: proof.metrics.physicsQueries,
302
+ subsystemKccCorrections: proof.metrics.kccCorrections,
303
+ subsystemNavReplans: proof.metrics.navReplans,
304
+ subsystemBotTraceRows: proof.metrics.botTraceRows,
305
+ subsystemAnimationEvents: proof.metrics.animationEvents,
306
+ subsystemReplayVerified: proof.replayVerified ? 1 : 0,
307
+ subsystemRollbackEquivalent: proof.rollbackEquivalent ? 1 : 0,
308
+ };
309
+ }
310
+ function stepIntegratedScene(options) {
311
+ let runtime = createIntegratedRuntime(options.descriptor, options.seed);
312
+ let physics = integratedPhysicsWorld();
313
+ let kcc = integratedKccBody();
314
+ let agents = [{ id: 'agent-a', polygonId: 'a', targetPolygonId: 'b', speedPerFrame: 1, corridor: ['a', 'b'], corridorIndex: 0 }];
315
+ let animationState = createDeterministicAnimationRuntimeState(options.animationDescriptor, { moving: false, fire: false });
316
+ let snapshot;
317
+ const inputHistory = [];
318
+ const commandHistory = [];
319
+ const snapshots = options.replayFrameLimit >= 0 ? [runtimeSnapshotForReplay(runtime)] : [];
320
+ let physicsContacts = 0;
321
+ let physicsQueries = 0;
322
+ let kccCorrections = 0;
323
+ let navReplans = 0;
324
+ let animationEvents = 0;
325
+ for (let frame = 0; frame < options.frames; frame += 1) {
326
+ if (frame === options.rollbackFrame) {
327
+ snapshot = {
328
+ runtimeSnapshot: runtime.snapshot(),
329
+ physics,
330
+ kcc,
331
+ agents,
332
+ animationState,
333
+ physicsContacts,
334
+ physicsQueries,
335
+ kccCorrections,
336
+ navReplans,
337
+ animationEvents,
338
+ };
339
+ }
340
+ if (snapshot !== undefined && frame === options.rollbackFrame + 3) {
341
+ runtime = createIntegratedRuntime(options.descriptor, options.seed);
342
+ runtime.restore(snapshot.runtimeSnapshot);
343
+ physics = cloneCanonical(snapshot.physics);
344
+ kcc = cloneCanonical(snapshot.kcc);
345
+ agents = cloneCanonical(snapshot.agents);
346
+ animationState = cloneCanonical(snapshot.animationState);
347
+ physicsContacts = snapshot.physicsContacts;
348
+ physicsQueries = snapshot.physicsQueries;
349
+ kccCorrections = snapshot.kccCorrections;
350
+ navReplans = snapshot.navReplans;
351
+ animationEvents = snapshot.animationEvents;
352
+ for (let replayFrame = options.rollbackFrame; replayFrame < frame; replayFrame += 1) {
353
+ const replayed = stepIntegratedSubsystems({
354
+ frame: replayFrame,
355
+ seed: options.seed,
356
+ physics,
357
+ kcc,
358
+ agents,
359
+ animationState,
360
+ navmesh: options.navmesh,
361
+ botTrace: options.botTrace,
362
+ animationDescriptor: options.animationDescriptor,
363
+ configHash: options.configHash,
364
+ });
365
+ physics = replayed.physics;
366
+ kcc = replayed.kcc;
367
+ agents = replayed.agents;
368
+ animationState = replayed.animationState;
369
+ physicsContacts += replayed.payload.physicsContacts;
370
+ physicsQueries += replayed.payload.physicsQueries;
371
+ kccCorrections += replayed.payload.kccCorrections;
372
+ navReplans += replayed.payload.navReplans;
373
+ animationEvents += replayed.payload.animationEvents;
374
+ injectIntegratedRuntimeFrame(runtime, replayFrame, [replayed.payload], options.commandTimeline.poll(replayFrame).map((command) => cloneCanonical(command)));
375
+ runtime.step(options.systems);
376
+ }
377
+ }
378
+ const stepped = stepIntegratedSubsystems({
379
+ frame,
380
+ seed: options.seed,
381
+ physics,
382
+ kcc,
383
+ agents,
384
+ animationState,
385
+ navmesh: options.navmesh,
386
+ botTrace: options.botTrace,
387
+ animationDescriptor: options.animationDescriptor,
388
+ configHash: options.configHash,
389
+ });
390
+ physics = stepped.physics;
391
+ kcc = stepped.kcc;
392
+ agents = stepped.agents;
393
+ animationState = stepped.animationState;
394
+ physicsContacts += stepped.payload.physicsContacts;
395
+ physicsQueries += stepped.payload.physicsQueries;
396
+ kccCorrections += stepped.payload.kccCorrections;
397
+ navReplans += stepped.payload.navReplans;
398
+ animationEvents += stepped.payload.animationEvents;
399
+ const commands = options.commandTimeline.poll(frame).map((command) => cloneCanonical(command));
400
+ if (frame < options.replayFrameLimit) {
401
+ inputHistory.push({ frame, inputs: [stepped.payload] });
402
+ commandHistory.push({ frame, commands });
403
+ }
404
+ injectIntegratedRuntimeFrame(runtime, frame, [stepped.payload], commands);
405
+ runtime.step(options.systems);
406
+ if (frame + 1 <= options.replayFrameLimit) {
407
+ snapshots.push(runtimeSnapshotForReplay(runtime));
408
+ }
409
+ }
410
+ return {
411
+ checksum: runtime.snapshot().checksum,
412
+ inputHistory,
413
+ commandHistory,
414
+ runtimeDebugEvents: runtime.debugEvents,
415
+ snapshots,
416
+ physicsContacts,
417
+ physicsQueries,
418
+ kccCorrections,
419
+ navReplans,
420
+ animationEvents,
421
+ };
422
+ }
423
+ function stepIntegratedSubsystems(options) {
424
+ const physicsStep = stepDeterministicPhysicsWorld3D(options.physics, {
425
+ scheduledQueries: [{
426
+ id: `ray-${options.frame}`,
427
+ phase: 'post-physics',
428
+ query: { x: -4, y: 0, z: 0, dx: 1, dy: 0, dz: 0, maxDistance: 16, layerMask: 1 },
429
+ }],
430
+ });
431
+ const kccStep = stepDeterministicKcc3D(options.kcc, {
432
+ moveX: (options.frame + options.seed) % 3 - 1,
433
+ moveZ: options.frame % 2,
434
+ jumpPressed: options.frame % 11 === 0,
435
+ }, integratedKccWorld(), { frame: options.frame, physics: physicsStep.world });
436
+ const navStep = stepDeterministicNavmeshAgents(options.navmesh, options.agents, {
437
+ movingObstaclePolygons: options.frame % 17 === 0 ? ['b'] : [],
438
+ });
439
+ const animationStep = stepDeterministicAnimation(options.animationDescriptor, options.animationState, {
440
+ moving: options.frame % 3 !== 0,
441
+ fire: options.frame % 5 === 0,
442
+ });
443
+ const botTrace = options.botTrace[options.frame % options.botTrace.length];
444
+ const payload = {
445
+ slot: 0,
446
+ physicsContacts: physicsStep.narrowphaseContacts,
447
+ physicsQueries: physicsStep.scheduledQueryResults.length,
448
+ kccCorrections: kccStep.correctionCount,
449
+ navReplans: navStep.replans,
450
+ botTicks: botTrace === undefined ? 0 : 1,
451
+ animationEvents: animationStep.events.length,
452
+ configHashNibble: parseInt(options.configHash.slice(0, 2), 16),
453
+ };
454
+ return {
455
+ physics: physicsStep.world,
456
+ kcc: kccStep.body,
457
+ agents: navStep.agents,
458
+ animationState: animationStep.state,
459
+ payload,
460
+ };
461
+ }
462
+ function integratedRuntimeDescriptor() {
463
+ return {
464
+ id: 'integrated-headless-scene',
465
+ tickRate: 60,
466
+ components: [{
467
+ id: 'scene',
468
+ singleton: true,
469
+ fields: [
470
+ { name: 'score', kind: 'int', defaultValue: 0 },
471
+ { name: 'inputDelta', kind: 'int', defaultValue: 0 },
472
+ { name: 'commandDelta', kind: 'int', defaultValue: 0 },
473
+ { name: 'frames', kind: 'int', defaultValue: 0 },
474
+ ],
475
+ }],
476
+ commandChannels: [{ id: 'ability', maxPayloadBytes: 128, reliable: true, allowPrediction: true }],
477
+ systems: [{ id: 'integrate', group: 'simulation', order: 0 }],
478
+ };
479
+ }
480
+ function integratedRuntimeSystems() {
481
+ return {
482
+ integrate(ctx) {
483
+ const scene = ctx.getSingleton('scene') ?? { score: 0, inputDelta: 0, commandDelta: 0, frames: 0 };
484
+ const score = Number(scene.score ?? 0) + Number(scene.inputDelta ?? 0) + Number(scene.commandDelta ?? 0);
485
+ ctx.setSingleton('scene', {
486
+ score,
487
+ inputDelta: 0,
488
+ commandDelta: 0,
489
+ frames: Number(scene.frames ?? 0) + 1,
490
+ });
491
+ if (ctx.frame < 128 || ctx.frame % 16 === 0) {
492
+ ctx.emitDebug('integrated.scene', { frame: ctx.frame, score });
493
+ }
494
+ },
495
+ };
496
+ }
497
+ function createIntegratedRuntime(descriptor, seed) {
498
+ const runtime = createDeterministicFrameRuntime(descriptor, seed);
499
+ runtime.setSingleton('scene', { score: 0, inputDelta: 0, commandDelta: 0, frames: 0 });
500
+ return runtime;
501
+ }
502
+ function injectIntegratedRuntimeFrame(runtime, _frame, inputs, commands) {
503
+ const scene = runtime.snapshot().singletonComponents.scene ?? { score: 0, frames: 0 };
504
+ const inputDelta = inputs.reduce((sum, input) => sum + integratedInputDelta(input), 0);
505
+ const commandDelta = commands.reduce((sum, command) => sum + integratedCommandDelta(command), 0);
506
+ runtime.setSingleton('scene', {
507
+ score: Number(scene.score ?? 0),
508
+ inputDelta,
509
+ commandDelta,
510
+ frames: Number(scene.frames ?? 0),
511
+ });
512
+ }
513
+ function integratedInputDelta(value) {
514
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
515
+ return 0;
516
+ }
517
+ const record = value;
518
+ return Number(record.physicsContacts ?? 0)
519
+ + Number(record.physicsQueries ?? 0)
520
+ + Number(record.kccCorrections ?? 0)
521
+ + Number(record.navReplans ?? 0)
522
+ + Number(record.botTicks ?? 0)
523
+ + Number(record.animationEvents ?? 0)
524
+ + Number(record.configHashNibble ?? 0);
525
+ }
526
+ function integratedCommandDelta(value) {
527
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
528
+ return 0;
529
+ }
530
+ const payload = value.payload;
531
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
532
+ return 0;
533
+ }
534
+ return Number(payload.power ?? 0);
535
+ }
536
+ function runtimeSnapshotForReplay(runtime) {
537
+ const snapshot = runtime.snapshot();
538
+ return { frame: snapshot.frame, checksum: snapshot.checksum, bytes: snapshot.bytes };
539
+ }
540
+ function recordIntegratedCommands(timeline, frames, players) {
541
+ for (let frame = 0; frame < frames; frame += 1) {
542
+ timeline.setCurrentFrame(frame);
543
+ const slot = frame % Math.max(1, players);
544
+ const commands = frame === 0
545
+ ? [
546
+ { id: 'integrated:0:dash', slot: 0, frame, channelId: 'ability', payload: { assetRef: 'ability.dash', power: 3 } },
547
+ { id: 'integrated:0:shield', slot: 0, frame, channelId: 'ability', payload: { assetRef: 'ability.dash', power: 5 } },
548
+ ]
549
+ : [{ id: `integrated:${frame}:dash`, slot, frame, channelId: 'ability', payload: { assetRef: 'ability.dash', power: (frame % 7) + 1 } }];
550
+ for (const command of commands) {
551
+ const rejection = timeline.recordFromSlot(command.slot, command);
552
+ if (rejection !== undefined) {
553
+ throw new Error(`Integrated scene command rejected ${rejection.commandId}:${rejection.reason}`);
554
+ }
555
+ }
556
+ }
557
+ }
558
+ function integratedPhysicsWorld() {
559
+ const bodies = [
560
+ { id: 'wall', kind: 'static', x: 2, y: 0, z: 0, vx: 0, vy: 0, vz: 0, shape: { type: 'box', halfX: 1, halfY: 3, halfZ: 1 }, layer: 1, mask: 1 },
561
+ { id: 'mover', kind: 'dynamic', x: -1, y: 0, z: 0, vx: 1, vy: 0, vz: 0, shape: { type: 'sphere', radius: 1 }, layer: 1, mask: 1 },
562
+ ];
563
+ return createDeterministicPhysicsWorld3D(bodies);
564
+ }
565
+ function integratedKccBody() {
566
+ return { x: -2, y: 4, z: 0, vx: 0, vy: -1, vz: 0, grounded: false, coyoteFrames: 0, jumpBufferFrames: 0 };
567
+ }
568
+ function integratedKccWorld() {
569
+ return {
570
+ minX: -8,
571
+ maxX: 24,
572
+ minY: 0,
573
+ maxY: 32,
574
+ minZ: -8,
575
+ maxZ: 16,
576
+ maxSlopeRisePerUnit: 1,
577
+ stepHeight: 2,
578
+ slopes: [{ id: 'integrated-slope', minX: -4, maxX: 0, minZ: -4, maxZ: 4, baseY: 0, risePerUnitX: 1 }],
579
+ steps: [{ id: 'integrated-step', minX: 3, maxX: 5, minZ: -2, maxZ: 2, height: 1 }],
580
+ };
581
+ }
582
+ function integratedAnimationDescriptor() {
583
+ return {
584
+ id: 'integrated-animation',
585
+ initialState: 'idle',
586
+ clips: [
587
+ { id: 'idle', durationFrames: 4, loop: true, events: [{ id: 'idle-loop', frame: 0 }], tracks: [{ id: 'root-x', keyframes: [{ frame: 0, value: 0 }, { frame: 3, value: 0 }] }] },
588
+ { id: 'run', durationFrames: 4, loop: true, events: [{ id: 'step', frame: 2 }], tracks: [{ id: 'root-x', keyframes: [{ frame: 0, value: 0 }, { frame: 3, value: 30 }] }] },
589
+ { id: 'fire', durationFrames: 3, loop: false, events: [{ id: 'shot', frame: 0 }], tracks: [{ id: 'root-x', keyframes: [{ frame: 0, value: 100 }, { frame: 2, value: 100 }] }] },
590
+ ],
591
+ states: [{ id: 'idle', clipId: 'idle' }, { id: 'run', clipId: 'run' }, { id: 'fire', clipId: 'fire' }],
592
+ transitions: [
593
+ { from: 'idle', to: 'fire', when: { parameter: 'fire', equals: true }, priority: 0, consumeTrigger: 'fire' },
594
+ { from: 'run', to: 'fire', when: { parameter: 'fire', equals: true }, priority: 0, consumeTrigger: 'fire' },
595
+ { from: 'idle', to: 'run', when: { parameter: 'moving', equals: true }, priority: 2 },
596
+ { from: 'run', to: 'idle', when: { parameter: 'moving', equals: false }, priority: 2 },
597
+ { from: 'fire', to: 'idle', when: { parameter: 'fire', equals: false }, priority: 3, exitFrame: 2 },
598
+ ],
599
+ };
600
+ }
601
+ function advanceSampleState(sampleId, state, frame, seed, metrics) {
602
+ const mode = sampleId.length % 5;
603
+ addSceneMetrics(sampleId, frame, seed, metrics);
604
+ return {
605
+ frame: frame + 1,
606
+ x: state.x + ((frame + seed + mode) % 3) - 1,
607
+ y: state.y + ((frame + seed + mode) % 5) - 2,
608
+ hp: Math.max(1, state.hp - ((frame + mode) % 23 === 0 ? 1 : 0)),
609
+ score: state.score + ((frame + seed + mode) % 17 === 0 ? 1 : 0),
610
+ phase: (state.phase + mode + 1) % 1024,
611
+ cooldown: Math.max(0, state.cooldown - 1) + ((frame + seed) % 29 === 0 ? 4 : 0),
612
+ };
613
+ }
614
+ function addSceneMetrics(sampleId, frame, seed, metrics) {
615
+ const add = (key, condition) => {
616
+ metrics[key] = (metrics[key] ?? 0) + (condition ? 1 : 0);
617
+ };
618
+ switch (sampleId) {
619
+ case 'asteroids':
620
+ add('shipRotations', true);
621
+ add('thrustFrames', frame % 2 === 0);
622
+ add('projectiles', frame % 7 === 0);
623
+ add('asteroidSplits', frame % 31 === 0);
624
+ add('collisions', frame % 37 === 0);
625
+ add('wraps', frame % 43 === 0);
626
+ add('scoreEvents', frame % 17 === 0);
627
+ return;
628
+ case 'simple-fps':
629
+ add('kccFrames', true);
630
+ add('hitscanShots', frame % 9 === 0);
631
+ add('lagCompRewinds', frame % 11 === 0);
632
+ add('pickups', frame % 23 === 0);
633
+ add('respawns', frame % 41 === 0);
634
+ add('weaponSwitches', frame % 13 === 0);
635
+ add('ammoEvents', frame % 7 === 0);
636
+ add('teamScores', frame % 19 === 0);
637
+ return;
638
+ case 'twin-stick':
639
+ add('topDownFrames', true);
640
+ add('projectileAbilities', frame % 8 === 0);
641
+ add('sensorHits', frame % 10 === 0);
642
+ add('botDecisions', true);
643
+ add('contextSteering', frame % 3 === 0);
644
+ add('pickups', frame % 21 === 0);
645
+ add('agents128', true);
646
+ return;
647
+ case 'platform-shooter':
648
+ add('slopes', frame % 5 === 0);
649
+ add('movingPlatforms', frame % 6 === 0);
650
+ add('projectileCollisions', frame % 9 === 0);
651
+ add('jumps', frame % 11 === 0);
652
+ add('rollbackContactFrames', frame % 13 === 0);
653
+ return;
654
+ case 'arena-brawler':
655
+ add('meleeArcs', frame % 6 === 0);
656
+ add('knockbacks', frame % 10 === 0);
657
+ add('stunWindows', frame % 14 === 0);
658
+ add('teamScores', frame % 19 === 0);
659
+ add('priorityResolutions', true);
660
+ return;
661
+ case 'vehicle':
662
+ add('steeringFrames', true);
663
+ add('accelerationFrames', frame % 2 === 0);
664
+ add('brakingFrames', frame % 5 === 0);
665
+ add('driftFrames', frame % 7 === 0);
666
+ add('checkpoints', frame % 17 === 0);
667
+ add('laps', frame % 53 === 0);
668
+ add('collisions', frame % 29 === 0);
669
+ return;
670
+ case 'mini-golf':
671
+ add('turnAdvances', frame % 23 === 0);
672
+ add('strokes', frame % 11 === 0);
673
+ add('hazards', frame % 37 === 0);
674
+ add('scoreEvents', frame % 13 === 0);
675
+ add('timerTicks', true);
676
+ return;
677
+ case 'waiting-room':
678
+ add('readyToggles', frame % 8 === 0);
679
+ add('slotAssignments', frame % 16 === 0);
680
+ add('countdownTicks', frame % 3 === 0);
681
+ add('replacements', frame % 41 === 0);
682
+ add('lateJoins', frame % 47 === 0);
683
+ return;
684
+ case 'boat-water':
685
+ add('waveSamples', true);
686
+ add('buoyancyFrames', true);
687
+ add('dragFrames', frame % 2 === 0);
688
+ add('collisions', frame % 31 === 0);
689
+ return;
690
+ case 'animator':
691
+ add('stateTransitions', frame % 12 === 0);
692
+ add('eventFrames', frame % 10 === 0);
693
+ add('rootMotionSteps', true);
694
+ add('rollbackSafeFrames', frame % 7 === 0);
695
+ return;
696
+ }
697
+ }
698
+ function sampleInvariants(sampleId, metrics, rollbackEquivalent) {
699
+ const invariants = {
700
+ rollbackEquivalent,
701
+ };
702
+ for (const key of requiredMetricKeys(sampleId)) {
703
+ invariants[key] = (metrics[key] ?? 0) > 0;
704
+ }
705
+ return Object.fromEntries(Object.entries(invariants).sort((left, right) => compareStrings(left[0], right[0])));
706
+ }
707
+ function compareStrings(left, right) {
708
+ return left < right ? -1 : left > right ? 1 : 0;
709
+ }
710
+ function requiredMetricKeys(sampleId) {
711
+ const subsystemBacked = [
712
+ 'subsystemFrameRuntimeFrames',
713
+ 'subsystemCommandsAccepted',
714
+ 'subsystemPhysicsContacts',
715
+ 'subsystemPhysicsQueries',
716
+ 'subsystemKccCorrections',
717
+ 'subsystemNavReplans',
718
+ 'subsystemBotTraceRows',
719
+ 'subsystemAnimationEvents',
720
+ 'subsystemReplayVerified',
721
+ 'subsystemRollbackEquivalent',
722
+ ];
723
+ switch (sampleId) {
724
+ case 'asteroids':
725
+ return ['shipRotations', 'thrustFrames', 'projectiles', 'asteroidSplits', 'collisions', 'wraps', 'scoreEvents'];
726
+ case 'simple-fps':
727
+ return ['kccFrames', 'hitscanShots', 'lagCompRewinds', 'pickups', 'respawns', 'weaponSwitches', 'ammoEvents', 'teamScores', ...subsystemBacked];
728
+ case 'twin-stick':
729
+ return ['topDownFrames', 'projectileAbilities', 'sensorHits', 'botDecisions', 'contextSteering', 'pickups', 'agents128'];
730
+ case 'platform-shooter':
731
+ return ['slopes', 'movingPlatforms', 'projectileCollisions', 'jumps', 'rollbackContactFrames', ...subsystemBacked];
732
+ case 'arena-brawler':
733
+ return ['meleeArcs', 'knockbacks', 'stunWindows', 'teamScores', 'priorityResolutions', ...subsystemBacked];
734
+ case 'vehicle':
735
+ return ['steeringFrames', 'accelerationFrames', 'brakingFrames', 'driftFrames', 'checkpoints', 'laps', 'collisions'];
736
+ case 'mini-golf':
737
+ return ['turnAdvances', 'strokes', 'hazards', 'scoreEvents', 'timerTicks'];
738
+ case 'waiting-room':
739
+ return ['readyToggles', 'slotAssignments', 'countdownTicks', 'replacements', 'lateJoins'];
740
+ case 'boat-water':
741
+ return ['waveSamples', 'buoyancyFrames', 'dragFrames', 'collisions'];
742
+ case 'animator':
743
+ return ['stateTransitions', 'eventFrames', 'rootMotionSteps', 'rollbackSafeFrames'];
744
+ }
745
+ }