@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,69 @@
1
+ import { type DeterminismCheckMode, type DeterminismDiagnostic } from './static-checker.js';
2
+ export interface DeterministicProjectCertificationPluginOptions {
3
+ readonly manifestPath: string;
4
+ readonly require?: readonly string[];
5
+ readonly frames?: number;
6
+ readonly agents?: number;
7
+ readonly slots?: readonly number[];
8
+ readonly outDir?: string;
9
+ readonly json?: string;
10
+ readonly cwd?: string;
11
+ readonly cliCommand?: readonly [string, ...string[]];
12
+ }
13
+ export interface DeterministicProjectCheckPluginOptions extends DeterministicProjectCertificationPluginOptions {
14
+ readonly certification?: string;
15
+ readonly summary?: string;
16
+ readonly summaryJson?: string;
17
+ readonly preflight?: string;
18
+ readonly preflightJson?: string;
19
+ readonly dist?: string;
20
+ readonly out?: string;
21
+ readonly junit?: string;
22
+ readonly releaseJson?: string;
23
+ readonly browserRuntime?: 'chromium' | 'webkit';
24
+ readonly browserJson?: string;
25
+ readonly playwrightModule?: string;
26
+ }
27
+ export interface SyncplayPluginOptions {
28
+ readonly mode?: DeterminismCheckMode;
29
+ readonly simulationEntries?: readonly string[];
30
+ readonly projectCertification?: DeterministicProjectCertificationPluginOptions;
31
+ readonly projectCheck?: DeterministicProjectCheckPluginOptions;
32
+ readonly apply?: MinimalVitePluginApply;
33
+ readonly enforce?: MinimalVitePluginEnforce;
34
+ }
35
+ export type MinimalVitePluginApply = 'serve' | 'build' | ((config: unknown, env: MinimalVitePluginEnvironment) => boolean);
36
+ export type MinimalVitePluginEnforce = 'pre' | 'post';
37
+ export interface MinimalVitePluginEnvironment {
38
+ readonly command: 'serve' | 'build';
39
+ readonly mode: string;
40
+ readonly isSsrBuild?: boolean;
41
+ readonly isPreview?: boolean;
42
+ }
43
+ export interface MinimalVitePlugin {
44
+ readonly name: string;
45
+ readonly apply?: MinimalVitePluginApply;
46
+ readonly enforce?: MinimalVitePluginEnforce;
47
+ buildStart(): Promise<void>;
48
+ closeBundle?(): Promise<void>;
49
+ }
50
+ export interface MinimalViteConfig {
51
+ readonly plugins?: readonly (MinimalVitePlugin | false | null | undefined)[];
52
+ readonly [key: string]: unknown;
53
+ }
54
+ export declare function rundotSyncplayPlugin(options: SyncplayPluginOptions): MinimalVitePlugin;
55
+ export declare function createSyncplayViteConfig(options: SyncplayPluginOptions): MinimalViteConfig;
56
+ export declare function withSyncplayViteConfig<TConfig extends MinimalViteConfig>(config: TConfig, options: SyncplayPluginOptions): TConfig & {
57
+ readonly plugins: readonly (MinimalVitePlugin | false | null | undefined)[];
58
+ };
59
+ export declare function formatDiagnostics(diagnostics: readonly DeterminismDiagnostic[]): string;
60
+ export declare function runProjectCertification(options: DeterministicProjectCertificationPluginOptions): Promise<void>;
61
+ export declare function createProjectCertificationCommand(options: DeterministicProjectCertificationPluginOptions): {
62
+ readonly executable: string;
63
+ readonly args: readonly string[];
64
+ };
65
+ export declare function runProjectCheck(options: DeterministicProjectCheckPluginOptions): Promise<void>;
66
+ export declare function createProjectCheckCommand(options: DeterministicProjectCheckPluginOptions): {
67
+ readonly executable: string;
68
+ readonly args: readonly string[];
69
+ };
@@ -0,0 +1,203 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { promisify } from 'node:util';
6
+ import { checkDeterminism } from './static-checker.js';
7
+ const execFileAsync = promisify(execFile);
8
+ const nodeProcess = process;
9
+ export function rundotSyncplayPlugin(options) {
10
+ return {
11
+ name: 'rundot-syncplay',
12
+ apply: options.apply,
13
+ enforce: options.enforce,
14
+ async buildStart() {
15
+ const simulationEntries = options.simulationEntries ?? [];
16
+ if (simulationEntries.length > 0) {
17
+ const diagnostics = await checkDeterminism({
18
+ paths: simulationEntries,
19
+ mode: options.mode ?? 'warn',
20
+ });
21
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === 'error');
22
+ if (errors.length > 0) {
23
+ throw new Error(formatDiagnostics(errors));
24
+ }
25
+ }
26
+ if (options.projectCertification !== undefined) {
27
+ await runProjectCertification(options.projectCertification);
28
+ }
29
+ },
30
+ async closeBundle() {
31
+ if (options.projectCheck !== undefined) {
32
+ await runProjectCheck(options.projectCheck);
33
+ }
34
+ },
35
+ };
36
+ }
37
+ export function createSyncplayViteConfig(options) {
38
+ return {
39
+ plugins: [rundotSyncplayPlugin(options)],
40
+ };
41
+ }
42
+ export function withSyncplayViteConfig(config, options) {
43
+ return {
44
+ ...config,
45
+ plugins: [
46
+ ...(config.plugins ?? []),
47
+ rundotSyncplayPlugin(options),
48
+ ],
49
+ };
50
+ }
51
+ export function formatDiagnostics(diagnostics) {
52
+ return diagnostics
53
+ .map((diagnostic) => {
54
+ return [
55
+ `${diagnostic.ruleId} ${diagnostic.severity.toUpperCase()} ${diagnostic.path}:${diagnostic.line}:${diagnostic.column}`,
56
+ diagnostic.message,
57
+ `Use: ${diagnostic.suggestion}`,
58
+ ].join('\n');
59
+ })
60
+ .join('\n\n');
61
+ }
62
+ export async function runProjectCertification(options) {
63
+ const command = createProjectCertificationCommand(options);
64
+ try {
65
+ await execFileAsync(command.executable, command.args, {
66
+ cwd: options.cwd,
67
+ maxBuffer: 20 * 1024 * 1024,
68
+ });
69
+ }
70
+ catch (error) {
71
+ throw new Error(await formatProjectCertificationError(error, options));
72
+ }
73
+ }
74
+ export function createProjectCertificationCommand(options) {
75
+ const [executable, ...prefixArgs] = options.cliCommand ?? defaultProjectCertificationCliCommand();
76
+ const args = [
77
+ ...prefixArgs,
78
+ 'project:certify',
79
+ options.manifestPath,
80
+ ];
81
+ pushStringFlag(args, 'require', options.require?.join(','));
82
+ pushIntegerFlag(args, 'frames', options.frames);
83
+ pushIntegerFlag(args, 'agents', options.agents);
84
+ pushStringFlag(args, 'slots', options.slots?.join(','));
85
+ pushStringFlag(args, 'out-dir', options.outDir);
86
+ pushStringFlag(args, 'json', options.json);
87
+ return { executable, args };
88
+ }
89
+ export async function runProjectCheck(options) {
90
+ const command = createProjectCheckCommand(options);
91
+ try {
92
+ await execFileAsync(command.executable, command.args, {
93
+ cwd: options.cwd,
94
+ maxBuffer: 20 * 1024 * 1024,
95
+ });
96
+ }
97
+ catch (error) {
98
+ throw new Error(await formatProjectCheckError(error, options));
99
+ }
100
+ }
101
+ export function createProjectCheckCommand(options) {
102
+ const [executable, ...prefixArgs] = options.cliCommand ?? defaultProjectCertificationCliCommand();
103
+ const args = [
104
+ ...prefixArgs,
105
+ 'project:check',
106
+ options.manifestPath,
107
+ ];
108
+ pushStringFlag(args, 'require', options.require?.join(','));
109
+ pushIntegerFlag(args, 'frames', options.frames);
110
+ pushIntegerFlag(args, 'agents', options.agents);
111
+ pushStringFlag(args, 'slots', options.slots?.join(','));
112
+ pushStringFlag(args, 'out-dir', options.outDir);
113
+ pushStringFlag(args, 'certification', options.certification);
114
+ pushStringFlag(args, 'summary', options.summary);
115
+ pushStringFlag(args, 'summary-json', options.summaryJson);
116
+ pushStringFlag(args, 'preflight', options.preflight);
117
+ pushStringFlag(args, 'preflight-json', options.preflightJson);
118
+ pushStringFlag(args, 'dist', options.dist);
119
+ pushStringFlag(args, 'out', options.out);
120
+ pushStringFlag(args, 'json', options.json);
121
+ pushStringFlag(args, 'junit', options.junit);
122
+ pushStringFlag(args, 'release-json', options.releaseJson);
123
+ pushStringFlag(args, 'browser-runtime', options.browserRuntime);
124
+ pushStringFlag(args, 'browser-json', options.browserJson);
125
+ pushStringFlag(args, 'playwright-module', options.playwrightModule);
126
+ return { executable, args };
127
+ }
128
+ function defaultProjectCertificationCliCommand() {
129
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
130
+ return [nodeProcess.execPath, resolve(moduleDir, '../cli.js')];
131
+ }
132
+ function pushStringFlag(args, name, value) {
133
+ if (value === undefined || value.length === 0) {
134
+ return;
135
+ }
136
+ args.push(`--${name}`, value);
137
+ }
138
+ function pushIntegerFlag(args, name, value) {
139
+ if (value === undefined) {
140
+ return;
141
+ }
142
+ args.push(`--${name}`, String(value));
143
+ }
144
+ async function formatProjectCertificationError(error, options) {
145
+ const reportFailures = await readProjectCertificationFailures(options);
146
+ const child = error;
147
+ const stdout = String(child.stdout).trim();
148
+ const stderr = String(child.stderr).trim();
149
+ return [
150
+ 'Deterministic project certification failed.',
151
+ child.message,
152
+ reportFailures.diagnostic,
153
+ reportFailures.failures.length === 0 ? undefined : `failures:\n${reportFailures.failures.join('\n')}`,
154
+ stdout.length === 0 ? undefined : `stdout:\n${stdout}`,
155
+ stderr.length === 0 ? undefined : `stderr:\n${stderr}`,
156
+ ].filter((line) => line !== undefined).join('\n\n');
157
+ }
158
+ async function readProjectCertificationFailures(options) {
159
+ if (options.json === undefined || options.json.length === 0) {
160
+ return { failures: [] };
161
+ }
162
+ try {
163
+ const path = options.cwd === undefined ? options.json : resolve(options.cwd, options.json);
164
+ const report = JSON.parse(await readFile(path, 'utf8'));
165
+ return { failures: (report.failures ?? []).filter((failure) => typeof failure === 'string') };
166
+ }
167
+ catch (error) {
168
+ return {
169
+ failures: [],
170
+ diagnostic: `report unreadable: ${String(error)}`,
171
+ };
172
+ }
173
+ }
174
+ async function formatProjectCheckError(error, options) {
175
+ const reportFailures = await readProjectCheckFailures(options);
176
+ const child = error;
177
+ const stdout = String(child.stdout).trim();
178
+ const stderr = String(child.stderr).trim();
179
+ return [
180
+ 'Deterministic project check failed.',
181
+ child.message,
182
+ reportFailures.diagnostic,
183
+ reportFailures.failures.length === 0 ? undefined : `failures:\n${reportFailures.failures.join('\n')}`,
184
+ stdout.length === 0 ? undefined : `stdout:\n${stdout}`,
185
+ stderr.length === 0 ? undefined : `stderr:\n${stderr}`,
186
+ ].filter((line) => line !== undefined).join('\n\n');
187
+ }
188
+ async function readProjectCheckFailures(options) {
189
+ if (options.json === undefined || options.json.length === 0) {
190
+ return { failures: [] };
191
+ }
192
+ try {
193
+ const path = options.cwd === undefined ? options.json : resolve(options.cwd, options.json);
194
+ const report = JSON.parse(await readFile(path, 'utf8'));
195
+ return { failures: (report.failures ?? []).filter((failure) => typeof failure === 'string') };
196
+ }
197
+ catch (error) {
198
+ return {
199
+ failures: [],
200
+ diagnostic: `report unreadable: ${String(error)}`,
201
+ };
202
+ }
203
+ }
@@ -0,0 +1,148 @@
1
+ import type { DeterministicMath } from './math.js';
2
+ import type { DeterministicRandom } from './random.js';
3
+ import type { CanonicalValue } from './canonical.js';
4
+ export type TickRate = 10 | 20 | 30 | 60;
5
+ export type ReplayRecordingMode = 'memory' | 'none';
6
+ export interface DeterministicStepContext {
7
+ readonly frame: number;
8
+ readonly dtTicks: 1;
9
+ readonly random: DeterministicRandom;
10
+ readonly math: DeterministicMath;
11
+ readonly commands: readonly CanonicalValue[];
12
+ }
13
+ export type DeterministicStep<State, Input> = (state: Readonly<State>, inputs: readonly Readonly<Input>[], frame: number, ctx: DeterministicStepContext) => State;
14
+ export interface DeterministicSessionConfig<State, Input> {
15
+ readonly tickRate: TickRate;
16
+ readonly playerCount: number;
17
+ readonly initialState: State;
18
+ readonly step: DeterministicStep<State, Input>;
19
+ readonly defaultInput: Input;
20
+ readonly inputSchemaId: string;
21
+ readonly stateSchemaId: string;
22
+ readonly deterministicVersion: string;
23
+ readonly engineIdentityHash?: string;
24
+ readonly assetDbHash?: string;
25
+ readonly configHash?: string;
26
+ readonly replayMetadata?: Readonly<Record<string, string | number | boolean>>;
27
+ readonly checksum?: (state: State) => string | number;
28
+ readonly canonicalizeState?: (state: State) => unknown;
29
+ readonly canonicalizeInput?: (input: Input) => unknown;
30
+ readonly hydrateInput?: (recordedInput: unknown) => Input;
31
+ readonly serializeState?: (state: State) => Uint8Array;
32
+ readonly deserializeState?: (bytes: Uint8Array) => State;
33
+ readonly checksumIntervalFrames?: number;
34
+ readonly snapshotBufferSize?: number;
35
+ readonly randomSeed?: number;
36
+ readonly maxCatchUpFrames?: number;
37
+ readonly replayRecordingMode?: ReplayRecordingMode;
38
+ readonly onInputFrameRecorded?: (inputFrame: RecordedInputFrame) => void;
39
+ readonly onCommandFrameRecorded?: (commandFrame: RecordedCommandFrame) => void;
40
+ readonly onChecksumRecorded?: (checksum: RecordedChecksum) => void;
41
+ readonly canonicalizeCommand?: (command: unknown) => unknown;
42
+ readonly runtimeGuards?: boolean;
43
+ }
44
+ export interface RecordedInputFrame {
45
+ readonly frame: number;
46
+ readonly inputs: readonly CanonicalValue[];
47
+ readonly inputFlags?: readonly RecordedInputFlags[];
48
+ }
49
+ export interface RecordedInputFlags {
50
+ readonly slot: number;
51
+ readonly playerPresent: boolean;
52
+ readonly repeatedPrevious: boolean;
53
+ readonly replacedByServer: boolean;
54
+ readonly commandInput: boolean;
55
+ }
56
+ export interface RecordedChecksum {
57
+ readonly frame: number;
58
+ readonly checksum: string;
59
+ }
60
+ export interface RecordedCommandFrame {
61
+ readonly frame: number;
62
+ readonly commands: readonly CanonicalValue[];
63
+ }
64
+ export interface RecordedDebugEvent {
65
+ readonly frame: number;
66
+ readonly type: string;
67
+ readonly payload: CanonicalValue;
68
+ }
69
+ export interface ReplayFile {
70
+ readonly formatVersion: 1;
71
+ readonly deterministicVersion: string;
72
+ readonly tickRate: TickRate;
73
+ readonly playerCount: number;
74
+ readonly inputSchemaId: string;
75
+ readonly stateSchemaId: string;
76
+ readonly engineIdentityHash?: string;
77
+ readonly assetDbHash?: string;
78
+ readonly configHash?: string;
79
+ readonly metadata?: Readonly<Record<string, string | number | boolean>>;
80
+ readonly randomSeed: number;
81
+ readonly initialState: CanonicalValue;
82
+ readonly inputFrames: readonly RecordedInputFrame[];
83
+ readonly commandHistory?: readonly RecordedCommandFrame[];
84
+ readonly checksums: readonly RecordedChecksum[];
85
+ readonly debugEvents?: readonly RecordedDebugEvent[];
86
+ }
87
+ /**
88
+ * The portable subset of a {@link FrameSnapshot} another session exported —
89
+ * everything a fresh session needs to hydrate mid-match (snapshot late-join).
90
+ * `stateBytes` is game-opaque (the game's serializeState seam produced it).
91
+ */
92
+ export interface ExternalSessionSnapshot<Input> {
93
+ readonly frame: number;
94
+ readonly stateBytes: Uint8Array;
95
+ readonly randomState: number;
96
+ readonly inputs: readonly Input[];
97
+ }
98
+ export interface FrameSnapshot<State, Input> {
99
+ readonly frame: number;
100
+ readonly inputs: readonly Input[];
101
+ readonly checksum: string;
102
+ readonly state: State;
103
+ readonly stateBytes: Uint8Array;
104
+ readonly randomState: number;
105
+ }
106
+ export interface SessionSchemaIdentity {
107
+ readonly inputSchemaId: string;
108
+ readonly stateSchemaId: string;
109
+ readonly deterministicVersion: string;
110
+ }
111
+ export interface OfflineSession<State, Input> {
112
+ readonly currentFrame: number;
113
+ readonly accumulatedMs: number;
114
+ /** The game-build identity from config — lets transport layers hash it without game code. */
115
+ readonly schemaIdentity: SessionSchemaIdentity;
116
+ setInput(slot: number, input: Input): void;
117
+ setInputFlags(slot: number, flags: Partial<Omit<RecordedInputFlags, 'slot'>>): void;
118
+ recordCommands(commands: readonly unknown[]): void;
119
+ update(deltaMs: number): number;
120
+ stepFrames(count: number): void;
121
+ getState(): State;
122
+ exportReplay(): ReplayFile;
123
+ getSnapshot(frame: number): FrameSnapshot<State, Input> | undefined;
124
+ restoreToFrame(frame: number): void;
125
+ /**
126
+ * Hydrate from a snapshot exported by ANOTHER session (snapshot late-join):
127
+ * jumps to `snapshot.frame`, restoring state (via the game's deserialize
128
+ * seam) and RNG. The hydrated frame seeds the rolling snapshot buffer.
129
+ * The session can no longer export a replay (its history does not start at
130
+ * tick 0) — `exportReplay()` throws after this.
131
+ */
132
+ restoreSnapshot(snapshot: ExternalSessionSnapshot<Input>): void;
133
+ getSnapshotFrames(): readonly number[];
134
+ }
135
+ export interface ReplayVerificationResult {
136
+ readonly frames: number;
137
+ readonly checksums: number;
138
+ readonly mismatches: number;
139
+ readonly finalChecksum: string;
140
+ readonly firstMismatch?: ReplayVerificationMismatch;
141
+ }
142
+ export interface ReplayVerificationMismatch {
143
+ readonly frame: number;
144
+ readonly expectedChecksum: string;
145
+ readonly actualChecksum: string;
146
+ readonly inputFrame: RecordedInputFrame;
147
+ readonly state: CanonicalValue;
148
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ export interface VenusMpBudgetOptions {
2
+ readonly players: number;
3
+ readonly hz: number;
4
+ readonly framesPerMessage: number;
5
+ readonly maxPlayers: number;
6
+ readonly rateLimitPerSecond: number;
7
+ readonly maxMessageBytes: number;
8
+ readonly seed: number;
9
+ }
10
+ export interface VenusMpBudgetResult {
11
+ readonly players: number;
12
+ readonly hz: number;
13
+ readonly framesPerMessage: number;
14
+ readonly maxPlayers: number;
15
+ readonly rateLimitPerSecond: number;
16
+ readonly maxMessageBytes: number;
17
+ readonly messagesPerSecondPerPlayer: string;
18
+ readonly roomInboundMessagesPerSecond: string;
19
+ readonly jsonMessageBytes: number;
20
+ readonly packedMessageBytes: number;
21
+ readonly jsonWithinMaxMessage: boolean;
22
+ readonly packedWithinMaxMessage: boolean;
23
+ readonly withinRateLimit: boolean;
24
+ readonly withinMaxPlayers: boolean;
25
+ readonly recommendation: 'pass' | 'increase-max-players' | 'increase-bundle-size' | 'compact-payload' | 'multiple-changes';
26
+ readonly checksum: string;
27
+ readonly result: 'pass' | 'fail';
28
+ }
29
+ export declare function runVenusMpBudgetLab(options: VenusMpBudgetOptions): VenusMpBudgetResult;
@@ -0,0 +1,122 @@
1
+ import { defaultChecksum } from './canonical.js';
2
+ import { DeterministicRandom } from './random.js';
3
+ import { makeLabInput, packLabInputFrame } from './sample-lab.js';
4
+ export function runVenusMpBudgetLab(options) {
5
+ validateOptions(options);
6
+ const random = new DeterministicRandom(options.seed);
7
+ const frames = Array.from({ length: options.framesPerMessage }, (_, frame) => ({
8
+ frame,
9
+ inputs: Array.from({ length: options.players }, () => makeLabInput(random)),
10
+ }));
11
+ const jsonPayload = {
12
+ type: 'message',
13
+ msgType: 'rdm.input.v1',
14
+ data: {
15
+ startFrame: 0,
16
+ frames,
17
+ },
18
+ };
19
+ const packedPayloadBytes = frames.reduce((sum, frame) => sum + packLabInputFrame(frame.inputs).byteLength, 0);
20
+ const packedEnvelopeBytes = JSON.stringify({
21
+ type: 'message',
22
+ msgType: 'rdm.input-packed.v1',
23
+ data: {
24
+ startFrame: 0,
25
+ frames: options.framesPerMessage,
26
+ bytes: packedPayloadBytes,
27
+ },
28
+ }).length;
29
+ const jsonMessageBytes = JSON.stringify(jsonPayload).length;
30
+ const packedMessageBytes = packedEnvelopeBytes + packedPayloadBytes;
31
+ const messagesPerSecondPerPlayer = options.hz / options.framesPerMessage;
32
+ const roomInboundMessagesPerSecond = messagesPerSecondPerPlayer * options.players;
33
+ const withinMaxPlayers = options.players <= options.maxPlayers;
34
+ const withinRateLimit = roomInboundMessagesPerSecond <= options.rateLimitPerSecond;
35
+ const jsonWithinMaxMessage = jsonMessageBytes <= options.maxMessageBytes;
36
+ const packedWithinMaxMessage = packedMessageBytes <= options.maxMessageBytes;
37
+ let result = 'fail';
38
+ if (withinMaxPlayers && withinRateLimit && (jsonWithinMaxMessage || packedWithinMaxMessage)) {
39
+ result = 'pass';
40
+ }
41
+ const recommendation = chooseRecommendation({
42
+ withinMaxPlayers,
43
+ withinRateLimit,
44
+ jsonWithinMaxMessage,
45
+ packedWithinMaxMessage,
46
+ });
47
+ const reportWithoutChecksum = {
48
+ ...options,
49
+ messagesPerSecondPerPlayer,
50
+ roomInboundMessagesPerSecond,
51
+ jsonMessageBytes,
52
+ packedMessageBytes,
53
+ jsonWithinMaxMessage,
54
+ packedWithinMaxMessage,
55
+ withinRateLimit,
56
+ withinMaxPlayers,
57
+ recommendation,
58
+ result,
59
+ };
60
+ return {
61
+ players: options.players,
62
+ hz: options.hz,
63
+ framesPerMessage: options.framesPerMessage,
64
+ maxPlayers: options.maxPlayers,
65
+ rateLimitPerSecond: options.rateLimitPerSecond,
66
+ maxMessageBytes: options.maxMessageBytes,
67
+ messagesPerSecondPerPlayer: messagesPerSecondPerPlayer.toFixed(2),
68
+ roomInboundMessagesPerSecond: roomInboundMessagesPerSecond.toFixed(2),
69
+ jsonMessageBytes,
70
+ packedMessageBytes,
71
+ jsonWithinMaxMessage,
72
+ packedWithinMaxMessage,
73
+ withinRateLimit,
74
+ withinMaxPlayers,
75
+ recommendation,
76
+ checksum: defaultChecksum(reportWithoutChecksum),
77
+ result,
78
+ };
79
+ }
80
+ function chooseRecommendation(config) {
81
+ const failures = [
82
+ !config.withinMaxPlayers,
83
+ !config.withinRateLimit,
84
+ !config.jsonWithinMaxMessage && !config.packedWithinMaxMessage,
85
+ ].filter(Boolean).length;
86
+ if (failures === 0) {
87
+ return 'pass';
88
+ }
89
+ if (failures > 1) {
90
+ return 'multiple-changes';
91
+ }
92
+ if (!config.withinMaxPlayers) {
93
+ return 'increase-max-players';
94
+ }
95
+ if (!config.withinRateLimit) {
96
+ return 'increase-bundle-size';
97
+ }
98
+ return 'compact-payload';
99
+ }
100
+ function validateOptions(options) {
101
+ if (!Number.isInteger(options.players) || options.players < 1 || options.players > 128) {
102
+ throw new Error('Venus MP budget players must be between 1 and 128');
103
+ }
104
+ if (![10, 20, 30, 60].includes(options.hz)) {
105
+ throw new Error('Venus MP budget hz must be 10, 20, 30, or 60');
106
+ }
107
+ if (!Number.isInteger(options.framesPerMessage) || options.framesPerMessage < 1 || options.framesPerMessage > 60) {
108
+ throw new Error('Venus MP budget framesPerMessage must be between 1 and 60');
109
+ }
110
+ if (!Number.isInteger(options.maxPlayers) || options.maxPlayers < 1) {
111
+ throw new Error('Venus MP budget maxPlayers must be positive');
112
+ }
113
+ if (!Number.isInteger(options.rateLimitPerSecond) || options.rateLimitPerSecond < 1) {
114
+ throw new Error('Venus MP budget rateLimitPerSecond must be positive');
115
+ }
116
+ if (!Number.isInteger(options.maxMessageBytes) || options.maxMessageBytes < 1) {
117
+ throw new Error('Venus MP budget maxMessageBytes must be positive');
118
+ }
119
+ if (!Number.isInteger(options.seed) || options.seed < 0 || options.seed > 0xffff_ffff) {
120
+ throw new Error('Venus MP budget seed must be a uint32 integer');
121
+ }
122
+ }
@@ -0,0 +1,49 @@
1
+ import { type IncomingMessage } from 'node:http';
2
+ export declare function webSocketAcceptKey(secWebSocketKey: string): string;
3
+ export declare function encodeWebSocketTextFrame(text: string): Uint8Array;
4
+ /** A minimal, bodyless close frame (opcode 0x8, FIN set). */
5
+ export declare function encodeWebSocketCloseFrame(): Uint8Array;
6
+ export interface DecodedWebSocketFrame {
7
+ readonly opcode: number;
8
+ readonly text: string;
9
+ readonly bytesRead: number;
10
+ }
11
+ export type WebSocketFrameReadResult = {
12
+ readonly kind: 'frame';
13
+ readonly frame: DecodedWebSocketFrame;
14
+ }
15
+ /** Not enough buffered bytes yet — wait for more data. */
16
+ | {
17
+ readonly kind: 'incomplete';
18
+ }
19
+ /** Malformed/unsupported frame — the connection must be closed, never crashed. */
20
+ | {
21
+ readonly kind: 'invalid';
22
+ readonly reason: string;
23
+ };
24
+ /**
25
+ * NEVER throws: this runs inside raw `socket.on('data')` handlers, where an
26
+ * uncaught throw is not routed to the 'error' listener and takes the process
27
+ * down — a handful of malformed bytes from any client would be an
28
+ * unauthenticated crash. Malformed input is reported as `kind: 'invalid'` so
29
+ * the receive loop can close the connection instead.
30
+ */
31
+ export declare function readWebSocketFrame(bytes: Uint8Array): WebSocketFrameReadResult;
32
+ export interface WebSocketConnection {
33
+ send(text: string): void;
34
+ onMessage(handler: (text: string) => void): void;
35
+ onClose(handler: () => void): void;
36
+ close(): void;
37
+ }
38
+ export interface WebSocketServerHandle {
39
+ readonly port: number;
40
+ readonly url: string;
41
+ close(): Promise<void>;
42
+ }
43
+ export interface WebSocketServerOptions {
44
+ readonly path?: string;
45
+ readonly port?: number;
46
+ onConnection(connection: WebSocketConnection, request: IncomingMessage): void;
47
+ }
48
+ export declare function startWebSocketServer(options: WebSocketServerOptions): Promise<WebSocketServerHandle>;
49
+ export declare function connectWebSocketClient(url: string): Promise<WebSocketConnection>;