@series-inc/rundot-syncplay 5.25.0-beta.7 → 5.26.0-beta.0

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 (79) hide show
  1. package/README.md +52 -21
  2. package/core/authoring.d.ts +30 -0
  3. package/core/deterministic-stdlib.d.ts +127 -0
  4. package/core/index.d.ts +390 -0
  5. package/core/native/component_runtime.cpp +341 -0
  6. package/core/native/component_runtime.hpp +112 -0
  7. package/core/native/deterministic_math.cpp +594 -0
  8. package/core/native/deterministic_math.hpp +21 -0
  9. package/core/native/kinetix-native-main.cpp +707 -0
  10. package/core/native/kinetix-native-runtime.cpp +20 -0
  11. package/core/native/kinetix-native-runtime.hpp +25 -0
  12. package/core/native/kinetix-render-projection.cpp +20 -0
  13. package/core/native/kinetix-render-projection.hpp +14 -0
  14. package/core/native/physics/abi.cpp +205 -0
  15. package/core/native/physics/abi.hpp +128 -0
  16. package/core/native/physics/physics.cpp +4821 -0
  17. package/core/native/physics/physics.hpp +222 -0
  18. package/core/physics.d.ts +372 -0
  19. package/core/runtime.d.ts +471 -0
  20. package/core/scripts/build-native.mjs +113 -0
  21. package/core/scripts/build-wasm-physics.mjs +172 -0
  22. package/core/scripts/emit-product-digests.mjs +58 -0
  23. package/core/scripts/generate-platformer2d-manifest.mjs +82 -0
  24. package/core/scripts/preflight.mjs +76 -0
  25. package/core/scripts/run-box3d-benchmark.mjs +452 -0
  26. package/core/src/index.mjs +128 -0
  27. package/core/src/kinetix-authoring.mjs +69 -0
  28. package/core/src/kinetix-auxiliary-assets.mjs +43 -0
  29. package/core/src/kinetix-baker.mjs +618 -0
  30. package/core/src/kinetix-deterministic-math.mjs +1044 -0
  31. package/core/src/kinetix-deterministic-stdlib.mjs +669 -0
  32. package/core/src/kinetix-legacy-profile-contracts.mjs +69 -0
  33. package/core/src/kinetix-native-product.mjs +36 -0
  34. package/core/src/kinetix-platformer2d-errors.mjs +25 -0
  35. package/core/src/kinetix-platformer2d-level.mjs +323 -0
  36. package/core/src/kinetix-platformer2d-mechanics.mjs +616 -0
  37. package/core/src/kinetix-platformer2d-registry.mjs +154 -0
  38. package/core/src/kinetix-platformer2d-runtime.mjs +1266 -0
  39. package/core/src/kinetix-platformer2d-source-manifest.generated.mjs +16 -0
  40. package/core/src/kinetix-product-contract.mjs +223 -0
  41. package/core/src/kinetix-project-compiler.mjs +105 -0
  42. package/core/src/kinetix-render-projection.mjs +28 -0
  43. package/core/src/kinetix-runtime-adapter-utils.mjs +270 -0
  44. package/core/src/kinetix-runtime-contract.mjs +54 -0
  45. package/core/src/kinetix-runtime-identity.mjs +12 -0
  46. package/core/src/kinetix-session-config.mjs +181 -0
  47. package/core/src/kinetix-source-snapshot.mjs +60 -0
  48. package/core/src/kinetix-world-kernel.mjs +580 -0
  49. package/core/src/kinetix-world-runtime.mjs +178 -0
  50. package/core/src/physics/index.mjs +3187 -0
  51. package/core/src/physics/mass-properties.mjs +472 -0
  52. package/core/src/physics/physics-wasm-module.generated.mjs +16 -0
  53. package/core/src/runtime.mjs +59 -0
  54. package/core/src/shared/f64-bits.mjs +14 -0
  55. package/core/src/shared/kinetix-envelope-v1.mjs +589 -0
  56. package/core/src/shared/render-records-v1.mjs +168 -0
  57. package/core/src/shared/sha256.mjs +73 -0
  58. package/dist/authority-room.d.ts +1 -1
  59. package/dist/authority-room.js +1 -1
  60. package/dist/dev-authority-room.js +1 -1
  61. package/dist/match-log.d.ts +1 -1
  62. package/dist/networked-client.d.ts +12 -6
  63. package/dist/networked-client.js +1 -0
  64. package/dist/platformer-content.js +1 -1
  65. package/dist/r3f-render-adapter.js +1 -1
  66. package/dist/replay.d.ts +1 -1
  67. package/dist/replay.js +1 -1
  68. package/dist/runner.d.ts +2 -1
  69. package/dist/runner.js +2 -0
  70. package/dist/runtime-session.d.ts +15 -8
  71. package/dist/runtime-session.js +12 -1
  72. package/dist/sdk-session.js +1 -0
  73. package/dist/session-snapshot.d.ts +1 -1
  74. package/dist/session-wire.d.ts +1 -1
  75. package/dist/session-wire.js +1 -1
  76. package/dist/tools/index.d.ts +2 -2
  77. package/dist/tools/index.js +1 -1
  78. package/dist/types.d.ts +1 -1
  79. package/package.json +52 -7
package/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # RUN Syncplay
2
2
 
3
- Syncplay is the orchestration layer over a signed Kinetix runtime. Kinetix owns
4
- deterministic state, RNG streams, stepping, opaque checkpoints, serialization,
5
- hydration, and checksums. Syncplay owns inputs, prediction, confirmation,
6
- rollback policy, replay logs, late join, secrets, and transport.
3
+ Syncplay is the orchestration layer over a signed deterministic runtime. The
4
+ deterministic core ships inside this package under the `/core` subpaths
5
+ (`@series-inc/rundot-syncplay/core/...`): it owns deterministic state, RNG
6
+ streams, stepping, opaque checkpoints, serialization, hydration, and checksums.
7
+ Syncplay owns inputs, prediction, confirmation, rollback policy, replay logs,
8
+ late join, secrets, and transport.
7
9
 
8
10
  > **Upgrading an existing game?** See
9
11
  > [MIGRATING-SESSION-PRIMITIVES.md](./MIGRATING-SESSION-PRIMITIVES.md). Two
@@ -15,9 +17,9 @@ rollback policy, replay logs, late join, secrets, and transport.
15
17
  | Concern | Owner |
16
18
  |---|---|
17
19
  | Committed source capture, upload authority, build lifecycle | Pack API + CLI |
18
- | Compilation, exact product schema, runtime identity | Kinetix compiler/baker |
20
+ | Compilation, exact product schema, runtime identity | core compiler/baker |
19
21
  | Artifact immutability, signing, key trust, catalog eligibility | Pack service + KMS/GCS/Firestore |
20
- | Deterministic state, RNG streams, step, checkpoint bytes, canonical checksum | Kinetix runtime |
22
+ | Deterministic state, RNG streams, step, checkpoint bytes, canonical checksum | core runtime |
21
23
  | Inputs, prediction, confirmation, rollback policy, replay log, late join, secrets, transport | Syncplay |
22
24
  | Rendering/interpolation/effects | Presentation adapters outside authoritative state |
23
25
 
@@ -25,10 +27,12 @@ There is one session factory:
25
27
 
26
28
  ```ts
27
29
  import { createSyncplaySession } from '@series-inc/rundot-syncplay/browser';
28
- import { createFixed1000Runtime } from '@series-inc/rundot-kinetix/runtime';
30
+ import { createInstalledPlatformer2dRuntime } from '@series-inc/rundot-syncplay/core/runtime';
29
31
 
30
- const runtime = createFixed1000Runtime({
31
- product,
32
+ const runtime = createInstalledPlatformer2dRuntime({
33
+ pack,
34
+ levelBytes,
35
+ identity,
32
36
  sessionConfigBytes,
33
37
  });
34
38
 
@@ -37,16 +41,19 @@ const session = createSyncplaySession(runtime, {
37
41
  defaultInput: { throttle: 0, steer: 0, brake: false },
38
42
  snapshotBufferSize: 256,
39
43
  checksumIntervalFrames: 30,
44
+ requires: { projection: true },
40
45
  });
41
46
  ```
42
47
 
43
- The runtime must implement `@series-inc/rundot-kinetix/runtime`. Syncplay never
48
+ The runtime must implement `@series-inc/rundot-syncplay/core/runtime`. Syncplay never
44
49
  accepts an initial state, reducer, or creator-authored step callback. There is no
45
- compatibility mode for the removed reducer API.
50
+ compatibility mode for the removed reducer API. A non-platformer game supplies
51
+ the same ABI through `createInstalledRuntimeAdapter` and binds its source with
52
+ `deriveCustomRuntimeIdentity` plus `kinetixRuntimeModuleManifest`.
46
53
 
47
54
  ## Runtime and snapshot contract
48
55
 
49
- Live play retains opaque Kinetix checkpoint handles. Rollback restores those
56
+ Live play retains opaque core checkpoint handles. Rollback restores those
50
57
  handles directly; it does not serialize or hydrate them. A v2 promotion or
51
58
  late-join snapshot contains one opaque `runtimeBytes` payload plus the signed
52
59
  runtime identity, canonical session-config digest, frame, and checksum. The
@@ -57,6 +64,23 @@ session-config bytes, asks the game for a matching runtime through its runtime
57
64
  factory, and rejects a locally loaded pack mismatch before frame 0. Replays use
58
65
  the same identity/config/factory path.
59
66
 
67
+ ### Capability requirements
68
+
69
+ Session policies and runner configs accept
70
+ `requires?: { commands?: boolean; projection?: boolean }`. Requirements are
71
+ checked when each offline or networked session is constructed, before any frame
72
+ advances:
73
+
74
+ | Requirement | Failure |
75
+ |---|---|
76
+ | `projection: true` without `runtime.project` | `SYNCPLAY_PROJECTION_UNSUPPORTED` |
77
+ | `commands: true` with no capabilities declaration | `SYNCPLAY_RUNTIME_CAPABILITY_UNKNOWN` |
78
+ | `commands: true` with `capabilities.commands !== true` | `SYNCPLAY_RUNTIME_COMMANDS_UNSUPPORTED` |
79
+
80
+ The runner forwards one `requires` policy through both `startOffline()` and
81
+ `startNetworked()`, including reconfigured network sessions. Unknown capability
82
+ metadata never silently passes negotiation.
83
+
60
84
  ## Presentation frames
61
85
 
62
86
  `session.getState()` returns a detached deep copy of the current frame, which
@@ -113,7 +137,7 @@ npm run test:core
113
137
 
114
138
  The core suite covers checkpoint ownership/release, frame-zero rollback, v2
115
139
  snapshot hydration, replay verification, authority persistence, latency/loss
116
- convergence, late join, spectators, bots, secrets, and a real Kinetix world
140
+ convergence, late join, spectators, bots, secrets, and a real core world
117
141
  driven through `createSyncplaySession`.
118
142
 
119
143
  ## Unified session runner
@@ -199,8 +223,8 @@ level length — those are game-specific fields inside `sessionConfigBytes`, and
199
223
  decode them with your own session-config schema:
200
224
 
201
225
  ```ts
202
- import { decodeKinetixSessionConfig } from '@series-inc/rundot-kinetix/runtime';
203
- import { kinetixPlatformer2dSessionConfigSchema } from '@series-inc/rundot-kinetix/runtime';
226
+ import { decodeKinetixSessionConfig } from '@series-inc/rundot-syncplay/core/runtime';
227
+ import { kinetixPlatformer2dSessionConfigSchema } from '@series-inc/rundot-syncplay/core/runtime';
204
228
 
205
229
  const prepareNetworkRuntime = async (descriptor, signal) => {
206
230
  const session = decodeKinetixSessionConfig(
@@ -241,8 +265,8 @@ permanently unjoinable. `preparePlatformerLevel(api, declaration)` resolves a pi
241
265
  and raises the typed `SYNCPLAY_LEVEL_CONTENT_CHANGED` (naming the entry id and the
242
266
  edit-vs-pin cause) rather than an opaque `KINETIX_AUX_ASSET_MISMATCH` deep inside
243
267
  runtime construction. `declaration.encodeLevel` is an **injected callback**, not a
244
- static Kinetix import, because Syncplay must compile against both the published
245
- Kinetix pin and an overlaid local build.
268
+ static core import, so Syncplay's compiled output never hard-binds a specific
269
+ core build.
246
270
 
247
271
  **Capacity and late-join budgets.** Snapshot transfer defaults are **256 KiB**
248
272
  chunks and a **4 MiB** total ceiling; the immutable level payload is fetched and
@@ -252,15 +276,20 @@ protocol/load ceiling, not a 60 Hz performance guarantee for an arbitrary game.
252
276
 
253
277
  ## Supported correctness kit
254
278
 
255
- `@series-inc/rundot-syncplay/testing` exposes exactly six supported functions —
256
- no browser or runtime APIs leak through it:
279
+ `@series-inc/rundot-syncplay/testing` exposes exactly nine supported
280
+ entry points — no browser or runtime APIs leak through it:
257
281
 
258
282
  ```ts
259
283
  import {
260
284
  assertDeterministic,
261
285
  assertLateJoinHydration,
286
+ assertPresentationParity,
262
287
  assertReplayVerifies,
288
+ assertRunnerLifecycle,
263
289
  assertTwoClientConvergence,
290
+ createSimulatedSyncplayMatch,
291
+ createSyncplayRunnerHarness,
292
+ runSyncplaySynctest,
264
293
  } from '@series-inc/rundot-syncplay/testing'
265
294
  ```
266
295
 
@@ -268,5 +297,7 @@ import {
268
297
  restore-resim / hydration / replay cert; `assertTwoClientConvergence` and
269
298
  `assertLateJoinHydration` run the **real** authority room and networked client
270
299
  over a deterministic simulated network (`createSimulatedSyncplayMatch`,
271
- `runSyncplaySynctest`). Failures name the phase, frame, and disagreeing
272
- checksums.
300
+ `runSyncplaySynctest`). `createSyncplayRunnerHarness`, `assertRunnerLifecycle`,
301
+ and `assertPresentationParity` cover the runner seam — a real runner driven on
302
+ a fully controlled clock against the simulated match. Failures name the phase,
303
+ frame, and disagreeing checksums.
@@ -0,0 +1,30 @@
1
+ export type KinetixInstalledProfileName = 'deterministic-f64' | 'fixed-1000-3d';
2
+
3
+ export type KinetixSessionField =
4
+ | { type: 'string'; maxBytes: number; required?: boolean }
5
+ | { type: 'integer'; min: number; max: number; required?: boolean }
6
+ | { type: 'boolean'; required?: boolean }
7
+ | { type: 'enum'; values: readonly string[]; required?: boolean };
8
+
9
+ export interface KinetixSessionConfigSchema {
10
+ id: string;
11
+ version: number;
12
+ maxBytes: number;
13
+ fields: Readonly<Record<string, KinetixSessionField>>;
14
+ }
15
+
16
+ export interface KinetixProjectDeclaration {
17
+ id: string;
18
+ installedProfile: KinetixInstalledProfileName;
19
+ tickRate: 10 | 20 | 30 | 60;
20
+ inputSchema: Readonly<Record<string, unknown>>;
21
+ sessionConfigSchema: KinetixSessionConfigSchema;
22
+ mechanics: readonly Readonly<Record<string, unknown>>[];
23
+ components: readonly Readonly<Record<string, unknown>>[];
24
+ systems: readonly Readonly<Record<string, unknown>>[];
25
+ presentationBindings: readonly Readonly<Record<string, unknown>>[];
26
+ }
27
+
28
+ export function defineKinetixProject<const Project extends KinetixProjectDeclaration>(
29
+ project: Project,
30
+ ): Readonly<Project>;
@@ -0,0 +1,127 @@
1
+ export interface DeterministicVec2 {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ }
5
+
6
+ export interface DeterministicVec3 {
7
+ readonly x: number;
8
+ readonly y: number;
9
+ readonly z: number;
10
+ }
11
+
12
+ export interface DeterministicQuaternion extends DeterministicVec3 {
13
+ readonly w: number;
14
+ }
15
+
16
+ export interface DeterministicMathApi {
17
+ readonly fixedScale: number;
18
+ readonly minFixed: number;
19
+ readonly maxFixed: number;
20
+ toFixed(value: number): number;
21
+ fromFixed(value: number): number;
22
+ add(a: number, b: number): number;
23
+ sub(a: number, b: number): number;
24
+ mul(a: number, b: number): number;
25
+ div(a: number, b: number): number;
26
+ saturatingAdd(a: number, b: number): number;
27
+ saturatingSub(a: number, b: number): number;
28
+ quantize(value: number, engine: number): number;
29
+ sqrt(value: number): number;
30
+ integerSqrt(value: number): number;
31
+ abs(value: number): number;
32
+ sign(value: number): number;
33
+ min(a: number, b: number): number;
34
+ max(a: number, b: number): number;
35
+ lerp(a: number, b: number, t: number): number;
36
+ mod(a: number, b: number): number;
37
+ inverseLerp(a: number, b: number, value: number): number;
38
+ remap(inMin: number, inMax: number, outMin: number, outMax: number, value: number): number;
39
+ smoothstep(edge0: number, edge1: number, value: number): number;
40
+ log2Fixed(value: number): number;
41
+ log(value: number): number;
42
+ exp(value: number): number;
43
+ sinTurns(turns: number): number;
44
+ cosTurns(turns: number): number;
45
+ tanTurns(turns: number): number;
46
+ atan2(y: number, x: number): number;
47
+ atan2Signed(y: number, x: number): number;
48
+ wrapTurns(turns: number): number;
49
+ angleDeltaTurns(a: number, b: number): number;
50
+ turnsToRadians(turns: number): number;
51
+ radiansToTurns(radians: number): number;
52
+ hypot(x: number, y: number): number;
53
+ vec2(x: number, y: number): DeterministicVec2;
54
+ vec2Add(a: DeterministicVec2, b: DeterministicVec2): DeterministicVec2;
55
+ vec2Sub(a: DeterministicVec2, b: DeterministicVec2): DeterministicVec2;
56
+ vec2Scale(value: DeterministicVec2, scale: number): DeterministicVec2;
57
+ vec2Dot(a: DeterministicVec2, b: DeterministicVec2): number;
58
+ vec2Cross(a: DeterministicVec2, b: DeterministicVec2): number;
59
+ vec2LengthSq(value: DeterministicVec2): number;
60
+ vec2Length(value: DeterministicVec2): number;
61
+ vec2DistanceSq(a: DeterministicVec2, b: DeterministicVec2): number;
62
+ vec2Distance(a: DeterministicVec2, b: DeterministicVec2): number;
63
+ vec2Normalize(value: DeterministicVec2): DeterministicVec2;
64
+ vec2Perp(value: DeterministicVec2): DeterministicVec2;
65
+ vec2Negate(value: DeterministicVec2): DeterministicVec2;
66
+ vec2Rotate(value: DeterministicVec2, turns: number): DeterministicVec2;
67
+ vec2FromAngleTurns(turns: number, length: number): DeterministicVec2;
68
+ vec2AngleTurns(value: DeterministicVec2): number;
69
+ vec2Lerp(a: DeterministicVec2, b: DeterministicVec2, t: number): DeterministicVec2;
70
+ vec3(x: number, y: number, z: number): DeterministicVec3;
71
+ vec3Add(a: DeterministicVec3, b: DeterministicVec3): DeterministicVec3;
72
+ vec3Sub(a: DeterministicVec3, b: DeterministicVec3): DeterministicVec3;
73
+ vec3Scale(value: DeterministicVec3, scale: number): DeterministicVec3;
74
+ vec3Dot(a: DeterministicVec3, b: DeterministicVec3): number;
75
+ vec3Cross(a: DeterministicVec3, b: DeterministicVec3): DeterministicVec3;
76
+ vec3LengthSq(value: DeterministicVec3): number;
77
+ vec3Length(value: DeterministicVec3): number;
78
+ vec3Distance(a: DeterministicVec3, b: DeterministicVec3): number;
79
+ vec3Normalize(value: DeterministicVec3): DeterministicVec3;
80
+ vec3Lerp(a: DeterministicVec3, b: DeterministicVec3, t: number): DeterministicVec3;
81
+ vec3Negate(value: DeterministicVec3): DeterministicVec3;
82
+ quatFromYawTurns(turns: number): DeterministicQuaternion;
83
+ quatLerp(a: DeterministicQuaternion, b: DeterministicQuaternion, t: number): DeterministicQuaternion;
84
+ clamp(value: number, min: number, max: number): number;
85
+ }
86
+
87
+ export interface FixedTurnsMathApi {
88
+ readonly ONE: number;
89
+ F(value: number): number;
90
+ mul(a: number, b: number): number;
91
+ div(a: number, b: number): number;
92
+ clamp(value: number, min: number, max: number): number;
93
+ fabs(value: number): number;
94
+ fsign(value: number): number;
95
+ lerp(a: number, b: number, t: number): number;
96
+ fsin(turns: number): number;
97
+ fcos(turns: number): number;
98
+ }
99
+
100
+ export interface DeterministicRandomSnapshot {
101
+ readonly state: number;
102
+ }
103
+
104
+ export function createDeterministicMath(
105
+ fixedScale?: number,
106
+ minFixed?: number,
107
+ maxFixed?: number,
108
+ ): DeterministicMathApi;
109
+
110
+ export function createFixedTurnsMath(scale: number): FixedTurnsMathApi;
111
+
112
+ export class DeterministicRandom {
113
+ state: number;
114
+ constructor(seed: number);
115
+ nextUint32(): number;
116
+ nextInt(minInclusive: number, maxInclusive: number): number;
117
+ nextFixed(scale?: number): number;
118
+ fork(label: string): DeterministicRandom;
119
+ nextBool(): boolean;
120
+ nextRangeFixed(minFixed: number, maxFixed: number): number;
121
+ nextAngleTurns(fixedScale: number): number;
122
+ pickOne<T>(items: readonly T[]): T;
123
+ weighted<T>(items: readonly T[], weights: readonly number[]): T;
124
+ gaussian(math: DeterministicMathApi): number;
125
+ snapshot(): DeterministicRandomSnapshot;
126
+ restore(snapshot: DeterministicRandomSnapshot): void;
127
+ }