@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,281 @@
1
+ import { cloneCanonical, defaultChecksum } from './canonical.js';
2
+ export function createDeterministicLifecycleRunner(descriptors, hooks) {
3
+ validateLifecycleDescriptors(descriptors, hooks);
4
+ const ordered = [...descriptors].sort(compareLifecycleDescriptors);
5
+ const descriptorById = new Map(ordered.map((descriptor) => [descriptor.id, descriptor]));
6
+ let frame = 0;
7
+ let initialized = new Set();
8
+ let enabled = new Set(ordered.filter((descriptor) => descriptor.startEnabled !== false).map((descriptor) => descriptor.id));
9
+ let pendingSignals = [];
10
+ let counters = new Map();
11
+ let log = [];
12
+ function context(systemId) {
13
+ return {
14
+ frame,
15
+ systemId,
16
+ emit(event) {
17
+ log.push(`${frame}:${systemId}:${event}`);
18
+ },
19
+ increment(key, amount = 1) {
20
+ counters.set(key, (counters.get(key) ?? 0) + amount);
21
+ },
22
+ signal(signal) {
23
+ pendingSignals.push(cloneCanonical(signal));
24
+ },
25
+ };
26
+ }
27
+ function isEffectivelyEnabled(systemId) {
28
+ const descriptor = descriptorById.get(systemId);
29
+ if (descriptor === undefined || !enabled.has(systemId)) {
30
+ return false;
31
+ }
32
+ return descriptor.parentId === undefined || isEffectivelyEnabled(descriptor.parentId);
33
+ }
34
+ return {
35
+ get frame() {
36
+ return frame;
37
+ },
38
+ enable(systemId) {
39
+ assertLifecycleSystem(descriptorById, systemId);
40
+ if (!enabled.has(systemId)) {
41
+ enabled.add(systemId);
42
+ hooks[systemId]?.onEnabled?.(context(systemId));
43
+ }
44
+ },
45
+ disable(systemId) {
46
+ assertLifecycleSystem(descriptorById, systemId);
47
+ if (enabled.has(systemId)) {
48
+ enabled.delete(systemId);
49
+ hooks[systemId]?.onDisabled?.(context(systemId));
50
+ }
51
+ },
52
+ signal(signal) {
53
+ pendingSignals.push(cloneCanonical(signal));
54
+ },
55
+ step() {
56
+ for (const descriptor of ordered) {
57
+ if (!initialized.has(descriptor.id)) {
58
+ initialized.add(descriptor.id);
59
+ hooks[descriptor.id]?.onInit?.(context(descriptor.id));
60
+ }
61
+ }
62
+ const signals = pendingSignals.sort(compareLifecycleSignals);
63
+ pendingSignals = [];
64
+ for (const descriptor of ordered) {
65
+ if (!isEffectivelyEnabled(descriptor.id)) {
66
+ continue;
67
+ }
68
+ const systemHooks = hooks[descriptor.id];
69
+ for (const signal of signals) {
70
+ systemHooks?.signal?.(context(descriptor.id), signal);
71
+ }
72
+ if (descriptor.signalsOnly !== true) {
73
+ systemHooks?.update?.(context(descriptor.id));
74
+ }
75
+ }
76
+ frame += 1;
77
+ },
78
+ snapshot() {
79
+ return lifecycleSnapshot(frame, initialized, enabled, counters, pendingSignals, log);
80
+ },
81
+ restore(snapshot) {
82
+ validateLifecycleSnapshot(snapshot, descriptorById);
83
+ frame = snapshot.frame;
84
+ initialized = new Set(snapshot.initialized);
85
+ enabled = new Set(snapshot.enabled);
86
+ counters = new Map(stableRecordKeys(snapshot.counters).map((key) => [key, snapshot.counters[key]]));
87
+ pendingSignals = snapshot.pendingSignals.map((signal) => cloneCanonical(signal));
88
+ log = [...snapshot.log];
89
+ },
90
+ };
91
+ }
92
+ export function runDeterministicLifecycleFixture(frames = 128) {
93
+ const descriptors = [
94
+ { id: 'root', order: 0 },
95
+ { id: 'child', order: 1, parentId: 'root' },
96
+ { id: 'late', order: 2, startEnabled: false },
97
+ { id: 'signals', order: 3, signalsOnly: true },
98
+ ];
99
+ const hooks = Object.fromEntries(descriptors.map((descriptor) => [descriptor.id, lifecycleCountingHooks(descriptor.id)]));
100
+ const runner = createDeterministicLifecycleRunner(descriptors, hooks);
101
+ let snapshot;
102
+ for (let frame = 0; frame < frames; frame += 1) {
103
+ if (frame === 4)
104
+ runner.enable('late');
105
+ if (frame === 8)
106
+ runner.disable('root');
107
+ if (frame === 12)
108
+ runner.enable('root');
109
+ if (frame % 5 === 0)
110
+ runner.signal({ id: 'tick', payload: { frame } });
111
+ if (frame === Math.floor(frames / 2))
112
+ snapshot = runner.snapshot();
113
+ runner.step();
114
+ }
115
+ const checksum = runner.snapshot().checksum;
116
+ if (snapshot === undefined) {
117
+ throw new Error('Lifecycle fixture did not capture snapshot');
118
+ }
119
+ const restored = createDeterministicLifecycleRunner(descriptors, hooks);
120
+ restored.restore(snapshot);
121
+ for (let frame = snapshot.frame; frame < frames; frame += 1) {
122
+ if (frame === 4)
123
+ restored.enable('late');
124
+ if (frame === 8)
125
+ restored.disable('root');
126
+ if (frame === 12)
127
+ restored.enable('root');
128
+ if (frame % 5 === 0)
129
+ restored.signal({ id: 'tick', payload: { frame } });
130
+ restored.step();
131
+ }
132
+ const restoredSnapshot = restored.snapshot();
133
+ return {
134
+ checksum,
135
+ restoredChecksum: restoredSnapshot.checksum,
136
+ rollbackEquivalent: checksum === restoredSnapshot.checksum,
137
+ ...summarizeDeterministicLifecycleCounters(restoredSnapshot.counters, restoredSnapshot.log),
138
+ };
139
+ }
140
+ export function summarizeDeterministicLifecycleCounters(counters, log) {
141
+ return {
142
+ initCalls: counters.init ?? 0,
143
+ updateCalls: counters.update ?? 0,
144
+ enabledCalls: counters.enabled ?? 0,
145
+ disabledCalls: counters.disabled ?? 0,
146
+ signalCalls: counters.signal ?? 0,
147
+ hierarchySkippedUpdates: countMissingChildUpdates(log, 8, 12),
148
+ startDisabledCovered: log.some((entry) => entry.endsWith('late:enabled')),
149
+ signalOnlySkippedUpdate: log.length > 0 && log.every((entry) => !entry.includes(':signals:update')),
150
+ orderStable: firstFrameOrder(log).join(',') === 'root,child',
151
+ };
152
+ }
153
+ function lifecycleCountingHooks(systemId) {
154
+ return {
155
+ onInit(ctx) {
156
+ ctx.increment('init');
157
+ ctx.emit('init');
158
+ },
159
+ onEnabled(ctx) {
160
+ ctx.increment('enabled');
161
+ ctx.emit('enabled');
162
+ },
163
+ onDisabled(ctx) {
164
+ ctx.increment('disabled');
165
+ ctx.emit('disabled');
166
+ },
167
+ update(ctx) {
168
+ ctx.increment('update');
169
+ ctx.emit('update');
170
+ },
171
+ signal(ctx, signal) {
172
+ ctx.increment('signal');
173
+ ctx.emit(`signal:${signal.id}:${systemId}`);
174
+ },
175
+ };
176
+ }
177
+ function lifecycleSnapshot(frame, initialized, enabled, counters, pendingSignals, log) {
178
+ const body = {
179
+ frame,
180
+ initialized: [...initialized].sort(compareStrings),
181
+ enabled: [...enabled].sort(compareStrings),
182
+ counters: Object.fromEntries([...counters.entries()].sort((left, right) => compareStrings(left[0], right[0]))),
183
+ pendingSignals: [...pendingSignals].sort(compareLifecycleSignals).map((signal) => cloneCanonical(signal)),
184
+ log: [...log],
185
+ };
186
+ return { ...body, checksum: defaultChecksum(body) };
187
+ }
188
+ function validateLifecycleDescriptors(descriptors, hooks) {
189
+ const ids = new Set();
190
+ for (const descriptor of descriptors) {
191
+ if (typeof descriptor.id !== 'string' || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(descriptor.id)) {
192
+ throw new Error(`Invalid lifecycle system id ${descriptor.id}`);
193
+ }
194
+ if (ids.has(descriptor.id)) {
195
+ throw new Error(`Duplicate lifecycle system id ${descriptor.id}`);
196
+ }
197
+ ids.add(descriptor.id);
198
+ if (!Number.isInteger(descriptor.order)) {
199
+ throw new Error(`Lifecycle system ${descriptor.id} order must be an integer`);
200
+ }
201
+ }
202
+ for (const descriptor of descriptors) {
203
+ if (descriptor.parentId !== undefined && typeof descriptor.parentId !== 'string') {
204
+ throw new Error(`Lifecycle system ${descriptor.id} references invalid parent ${descriptor.parentId}`);
205
+ }
206
+ if (descriptor.parentId !== undefined && !ids.has(descriptor.parentId)) {
207
+ throw new Error(`Lifecycle system ${descriptor.id} references missing parent ${descriptor.parentId}`);
208
+ }
209
+ if (hooks[descriptor.id] === undefined) {
210
+ throw new Error(`Lifecycle system ${descriptor.id} missing hooks`);
211
+ }
212
+ }
213
+ for (const descriptor of descriptors) {
214
+ validateParentChain(descriptor.id, new Set(), descriptors);
215
+ }
216
+ }
217
+ function validateParentChain(systemId, seen, descriptors) {
218
+ if (seen.has(systemId)) {
219
+ throw new Error(`Lifecycle system ${systemId} parent chain contains a cycle`);
220
+ }
221
+ seen.add(systemId);
222
+ const descriptor = descriptors.find((candidate) => candidate.id === systemId);
223
+ if (descriptor?.parentId !== undefined) {
224
+ validateParentChain(descriptor.parentId, seen, descriptors);
225
+ }
226
+ seen.delete(systemId);
227
+ }
228
+ function validateLifecycleSnapshot(snapshot, descriptors) {
229
+ if (!Number.isInteger(snapshot.frame) || snapshot.frame < 0) {
230
+ throw new Error('Lifecycle snapshot frame must be a non-negative integer');
231
+ }
232
+ for (const systemId of [...snapshot.initialized, ...snapshot.enabled]) {
233
+ if (!descriptors.has(systemId)) {
234
+ throw new Error(`Lifecycle snapshot references unknown system ${systemId}`);
235
+ }
236
+ }
237
+ for (const key of stableRecordKeys(snapshot.counters)) {
238
+ const value = snapshot.counters[key];
239
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
240
+ throw new Error(`Lifecycle snapshot counter ${key} must be finite`);
241
+ }
242
+ }
243
+ const expected = lifecycleSnapshot(snapshot.frame, new Set(snapshot.initialized), new Set(snapshot.enabled), new Map(stableRecordKeys(snapshot.counters).map((key) => [key, snapshot.counters[key]])), snapshot.pendingSignals, snapshot.log).checksum;
244
+ if (snapshot.checksum !== expected) {
245
+ throw new Error('Lifecycle snapshot checksum mismatch');
246
+ }
247
+ }
248
+ function assertLifecycleSystem(descriptors, systemId) {
249
+ if (!descriptors.has(systemId)) {
250
+ throw new Error(`Unknown lifecycle system ${systemId}`);
251
+ }
252
+ }
253
+ function countMissingChildUpdates(log, startFrame, endFrame) {
254
+ let missing = 0;
255
+ const observedFrames = new Set(log.map((entry) => Number(entry.split(':')[0])).filter((frame) => Number.isInteger(frame)));
256
+ for (let frame = startFrame; frame < endFrame; frame += 1) {
257
+ if (observedFrames.has(frame) && !log.includes(`${frame}:child:update`)) {
258
+ missing += 1;
259
+ }
260
+ }
261
+ return missing;
262
+ }
263
+ function firstFrameOrder(log) {
264
+ return log
265
+ .filter((entry) => entry.startsWith('0:') && entry.endsWith(':update'))
266
+ .map((entry) => entry.split(':')[1]);
267
+ }
268
+ function compareLifecycleDescriptors(left, right) {
269
+ return left.order - right.order || compareStrings(left.id, right.id);
270
+ }
271
+ function compareLifecycleSignals(left, right) {
272
+ return compareStrings(left.id, right.id) || compareStrings(defaultChecksum(left.payload), defaultChecksum(right.payload));
273
+ }
274
+ function compareStrings(left, right) {
275
+ return left < right ? -1 : left > right ? 1 : 0;
276
+ }
277
+ function stableRecordKeys(record) {
278
+ return Reflect.ownKeys(record)
279
+ .filter((key) => typeof key === 'string')
280
+ .sort(compareStrings);
281
+ }
@@ -0,0 +1,17 @@
1
+ export interface DeterministicCooldown {
2
+ readonly remainingFrames: number;
3
+ readonly ready: boolean;
4
+ }
5
+ export interface ScheduledFrameEvent<Event> {
6
+ readonly frame: number;
7
+ readonly event: Event;
8
+ }
9
+ export declare function framesToMilliseconds(frames: number, tickRate: number): number;
10
+ export declare function millisecondsToFrames(milliseconds: number, tickRate: number): number;
11
+ export declare function createCooldown(durationFrames: number): DeterministicCooldown;
12
+ export declare function tickCooldown(cooldown: DeterministicCooldown, frames?: number): DeterministicCooldown;
13
+ export declare function scheduleFrameEvent<Event>(currentFrame: number, delayFrames: number, event: Event): ScheduledFrameEvent<Event>;
14
+ export declare function popDueFrameEvents<Event>(currentFrame: number, events: readonly ScheduledFrameEvent<Event>[]): {
15
+ readonly due: readonly ScheduledFrameEvent<Event>[];
16
+ readonly pending: readonly ScheduledFrameEvent<Event>[];
17
+ };
package/dist/timers.js ADDED
@@ -0,0 +1,53 @@
1
+ export function framesToMilliseconds(frames, tickRate) {
2
+ assertNonNegativeInteger('frames', frames);
3
+ assertPositiveInteger('tickRate', tickRate);
4
+ return Math.trunc((frames * 1000) / tickRate);
5
+ }
6
+ export function millisecondsToFrames(milliseconds, tickRate) {
7
+ if (!Number.isFinite(milliseconds) || milliseconds < 0) {
8
+ throw new Error('milliseconds must be a finite non-negative number');
9
+ }
10
+ assertPositiveInteger('tickRate', tickRate);
11
+ return Math.ceil((milliseconds * tickRate) / 1000);
12
+ }
13
+ export function createCooldown(durationFrames) {
14
+ assertNonNegativeInteger('durationFrames', durationFrames);
15
+ return {
16
+ remainingFrames: durationFrames,
17
+ ready: durationFrames === 0,
18
+ };
19
+ }
20
+ export function tickCooldown(cooldown, frames = 1) {
21
+ assertNonNegativeInteger('frames', frames);
22
+ const remainingFrames = Math.max(0, cooldown.remainingFrames - frames);
23
+ return {
24
+ remainingFrames,
25
+ ready: remainingFrames === 0,
26
+ };
27
+ }
28
+ export function scheduleFrameEvent(currentFrame, delayFrames, event) {
29
+ assertNonNegativeInteger('currentFrame', currentFrame);
30
+ assertNonNegativeInteger('delayFrames', delayFrames);
31
+ return {
32
+ frame: currentFrame + delayFrames,
33
+ event,
34
+ };
35
+ }
36
+ export function popDueFrameEvents(currentFrame, events) {
37
+ assertNonNegativeInteger('currentFrame', currentFrame);
38
+ const sorted = [...events].sort((left, right) => left.frame - right.frame);
39
+ return {
40
+ due: sorted.filter((entry) => entry.frame <= currentFrame),
41
+ pending: sorted.filter((entry) => entry.frame > currentFrame),
42
+ };
43
+ }
44
+ function assertNonNegativeInteger(label, value) {
45
+ if (!Number.isInteger(value) || value < 0) {
46
+ throw new Error(`${label} must be a non-negative integer`);
47
+ }
48
+ }
49
+ function assertPositiveInteger(label, value) {
50
+ if (!Number.isInteger(value) || value <= 0) {
51
+ throw new Error(`${label} must be a positive integer`);
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ export { checkDeterminism, checkSourceText, collectDeterminismSuppressions, type DeterminismCheckMode, type DeterminismCheckOptions, type DeterminismDiagnostic, type DeterminismParserKind, type DeterminismSeverity, type DeterminismSuppression, } from './static-checker.js';
2
+ export { createSyncplayViteConfig, createProjectCertificationCommand, createProjectCheckCommand, formatDiagnostics, runProjectCheck, runProjectCertification, rundotSyncplayPlugin, withSyncplayViteConfig, type DeterministicProjectCertificationPluginOptions, type DeterministicProjectCheckPluginOptions, type SyncplayPluginOptions, type MinimalViteConfig, type MinimalVitePlugin, type MinimalVitePluginApply, type MinimalVitePluginEnforce, type MinimalVitePluginEnvironment, } from './vite-plugin.js';
3
+ export { createDefaultCertificationManifest, runCertificationSuite, stableCertificationJson, } from '../certification.js';
4
+ export { runSyncplaySynctest, type SyncplaySynctestConfig, type SyncplaySynctestDivergence, type SyncplaySynctestInputFuzz, type SyncplaySynctestPhase, type SyncplaySynctestResult, } from '../synctest.js';
@@ -0,0 +1,4 @@
1
+ export { checkDeterminism, checkSourceText, collectDeterminismSuppressions, } from './static-checker.js';
2
+ export { createSyncplayViteConfig, createProjectCertificationCommand, createProjectCheckCommand, formatDiagnostics, runProjectCheck, runProjectCertification, rundotSyncplayPlugin, withSyncplayViteConfig, } from './vite-plugin.js';
3
+ export { createDefaultCertificationManifest, runCertificationSuite, stableCertificationJson, } from '../certification.js';
4
+ export { runSyncplaySynctest, } from '../synctest.js';
@@ -0,0 +1,31 @@
1
+ export type DeterminismSeverity = 'warn' | 'error';
2
+ export type DeterminismCheckMode = 'warn' | 'strict';
3
+ export interface DeterminismDiagnostic {
4
+ readonly ruleId: string;
5
+ readonly severity: DeterminismSeverity;
6
+ readonly path: string;
7
+ readonly line: number;
8
+ readonly column: number;
9
+ readonly message: string;
10
+ readonly suggestion: string;
11
+ }
12
+ export interface DeterminismSuppression {
13
+ readonly ruleId: string;
14
+ readonly path: string;
15
+ readonly line: number;
16
+ readonly reason: string;
17
+ }
18
+ export interface DeterminismCheckOptions {
19
+ readonly paths: readonly string[];
20
+ readonly mode?: DeterminismCheckMode;
21
+ readonly followImports?: boolean;
22
+ readonly parserKind?: DeterminismParserKind;
23
+ readonly project?: string;
24
+ readonly schemaStateFields?: readonly string[];
25
+ readonly stateObjectName?: string;
26
+ }
27
+ export type DeterminismParserKind = 'typescript-ast' | 'regex-scanner';
28
+ export declare function checkDeterminism(options: DeterminismCheckOptions): Promise<readonly DeterminismDiagnostic[]>;
29
+ export declare function collectDeterminismSuppressions(paths: readonly string[]): Promise<readonly DeterminismSuppression[]>;
30
+ export declare function checkSourceText(path: string, content: string, mode?: DeterminismCheckMode, options?: Pick<DeterminismCheckOptions, 'parserKind' | 'schemaStateFields' | 'stateObjectName'>): readonly DeterminismDiagnostic[];
31
+ export declare function getDeterminismParserKind(): DeterminismParserKind;