@series-inc/rundot-syncplay 5.25.0-beta.3 → 5.25.0-beta.4

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.
package/README.md CHANGED
@@ -110,3 +110,87 @@ The core suite covers checkpoint ownership/release, frame-zero rollback, v2
110
110
  snapshot hydration, replay verification, authority persistence, latency/loss
111
111
  convergence, late join, spectators, bots, secrets, and a real Kinetix world
112
112
  driven through `createSyncplaySession`.
113
+
114
+ ## Unified session runner
115
+
116
+ `createSyncplayRunner` owns the offline / co-op / networked lifecycle behind one
117
+ runtime factory and one presentation contract, so a game does not re-implement
118
+ create/join resolution, the local/network tick branch, bot filling, projection
119
+ extraction, and stale-connection invalidation:
120
+
121
+ ```ts
122
+ const runner = createSyncplayRunner({
123
+ runtimeFactory,
124
+ prepareNetworkRuntime: (descriptor, signal) => prepareLevelFromUgc(descriptor, signal),
125
+ defaultInput,
126
+ encodeInput,
127
+ decodeInput,
128
+ presentation: {
129
+ project: (frame) => frame,
130
+ interpolate: interpolateRenderState,
131
+ events: (frame) => frame.events,
132
+ },
133
+ })
134
+ runner.startOffline({ identity, sessionConfigBytes, playerCount: 2, botInputForTick })
135
+ await runner.startNetworked(() => createSyncplayRoom(api.realtime, roomOptions))
136
+ runner.applyInput(readInput())
137
+ runner.update(deltaMs)
138
+ render(runner.getRenderState(), runner.renderAlpha)
139
+ ```
140
+
141
+ **Status** is exactly `offline | connecting | syncing | live | stopped | error`:
142
+
143
+ - `connecting` covers the room-open promise.
144
+ - `syncing` covers session-descriptor/asset preparation, reconnect, and catch-up.
145
+ - `live` requires `client.caughtUp` plus at least one applied authority frame.
146
+ - `offline` owns the local fixed-step accumulator and bot filling.
147
+ - `error` carries connection, preparation, and offline-construction failures on
148
+ the runner snapshot (and the throw is rethrown to the caller).
149
+ - `stop` aborts preparation, closes the transport, and disposes the active
150
+ client/session — idempotently.
151
+
152
+ Presentation stays **predicted** for render motion, while side-effect events are
153
+ delivered only from authority-confirmed frames received after the first live
154
+ boundary. Initial and reconnect catch-up history is silent; a post-live
155
+ authority-leading frame still emits its event exactly once. Interpolation is
156
+ presentation-only and never feeds simulation. Starting a new mode always tears
157
+ down the old one, and a stale async connection/preparation closes without
158
+ replacing the current session.
159
+
160
+ Direct `NetworkedSyncplayClient` users get the same projection surface:
161
+ `getPresentationFrame()`, `renderAlpha`, `currentFrame`, `caughtUp`, and a
162
+ bounded confirmed-frame queue via the optional `confirmedPresentationBufferSize`
163
+ (absent = no queue, preserving existing cost) drained with
164
+ `drainConfirmedPresentationFrames()`. Call `beginPresentationCatchUp()` from your
165
+ transport's reconnect signal so replayed history never fills the queue.
166
+ `prepareNetworkedSyncplayTransport(transport, prepare, { signal })` buffers the
167
+ ordered `session-start`/history traffic (same 8,192-message ceiling as the SDK
168
+ transport), exposes a frozen descriptor with `levelEntryId + digest + length`,
169
+ awaits your async preparation, then replays every buffered message once.
170
+
171
+ **Capacity and late-join budgets.** Snapshot transfer defaults are **256 KiB**
172
+ chunks and a **4 MiB** total ceiling; the immutable level payload is fetched and
173
+ verified separately and never rides a snapshot. `maxPredictionTicks` is a
174
+ rollback/latency budget, not a player-count limit. 128 slots is the tested
175
+ protocol/load ceiling, not a 60 Hz performance guarantee for an arbitrary game.
176
+
177
+ ## Supported correctness kit
178
+
179
+ `@series-inc/rundot-syncplay/testing` exposes exactly six supported functions —
180
+ no browser or runtime APIs leak through it:
181
+
182
+ ```ts
183
+ import {
184
+ assertDeterministic,
185
+ assertLateJoinHydration,
186
+ assertReplayVerifies,
187
+ assertTwoClientConvergence,
188
+ } from '@series-inc/rundot-syncplay/testing'
189
+ ```
190
+
191
+ `assertDeterministic` and `assertReplayVerifies` run the straight-run /
192
+ restore-resim / hydration / replay cert; `assertTwoClientConvergence` and
193
+ `assertLateJoinHydration` run the **real** authority room and networked client
194
+ over a deterministic simulated network (`createSimulatedSyncplayMatch`,
195
+ `runSyncplaySynctest`). Failures name the phase, frame, and disagreeing
196
+ checksums.
package/dist/browser.d.ts CHANGED
@@ -19,7 +19,8 @@ export { rewindDeterministicTargets, runDeterministicMovementCrowdSimulation, ru
19
19
  export { stepDeterministicCarry3D, stepDeterministicKcc3D } from './movement3d.js';
20
20
  export { applyDeterministicReciprocalAvoidance, cookDeterministicNavigationGraph, cookTilemapNavigationGraph, createDeterministicFlowField, findDeterministicPath, smoothDeterministicPath, stepDeterministicNavigationAgents } from './navigation.js';
21
21
  export { cookDeterministicNavmesh, cookImportedDeterministicNavmesh, findDeterministicNavmeshPath, findDeterministicNavmeshPointPath, locateDeterministicNavmeshPolygon, runDeterministicNavmeshStress, smoothDeterministicNavmeshPath, stepDeterministicNavmeshAgents, stepDeterministicNavmeshOffMeshTraversal } from './navmesh.js';
22
- export { createNetworkedSyncplayClient } from './networked-client.js';
22
+ export { createNetworkedSyncplayClient, prepareNetworkedSyncplayTransport } from './networked-client.js';
23
+ export { createSyncplayRunner } from './runner.js';
23
24
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
24
25
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
25
26
  export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
@@ -58,7 +59,8 @@ export type { DeterministicFpsAimState, DeterministicFpsInput, DeterministicKccB
58
59
  export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
59
60
  export type { CookedDeterministicNavigationGraph, DeterministicFlowField, DeterministicNavEdge, DeterministicNavigationAgent, DeterministicNavigationGraphSource, DeterministicNavigationStepOptions, DeterministicNavigationStepResult, DeterministicNavNode, DeterministicNavRegion, DeterministicPathQuery, DeterministicPathResult, DeterministicTilemapSource } from './navigation.js';
60
61
  export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, DeterministicNavmeshAgentStepResult, DeterministicNavmeshImportSource, DeterministicNavmeshLink, DeterministicNavmeshMovingAvoidanceObstacle, DeterministicNavmeshOffMeshTraversalState, DeterministicNavmeshOffMeshTraversalStep, DeterministicNavmeshPath, DeterministicNavmeshPoint, DeterministicNavmeshPolygon, DeterministicNavmeshSource } from './navmesh.js';
61
- export type { NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplaySlotPresence } from './networked-client.js';
62
+ export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, NetworkedSyncplaySlotPresence, PreparedNetworkedSyncplayTransport } from './networked-client.js';
63
+ export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
62
64
  export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsActivePair2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
63
65
  export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
64
66
  export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D } from './physics3d-voxel.js';
@@ -87,3 +89,5 @@ export { deriveCoarseRegion, detectCoarseRegion } from './coarse-region.js';
87
89
  export type { CoarseRegion } from './coarse-region.js';
88
90
  export { WsMultiplayerApi } from '@series-inc/rundot-game-sdk/mp-client';
89
91
  export type { MultiplayerApi, JoinTicketRequest } from '@series-inc/rundot-game-sdk/mp-client';
92
+ export { openSyncplayBrowserRoom, resolveSyncplayBrowserIntent, shareSyncplayRoom, } from './browserSession.js';
93
+ export type { OpenSyncplayBrowserRoomOptions, OpenSyncplayBrowserRoomResult, ResolveSyncplayBrowserIntentOptions, SyncplayBrowserApi, SyncplayBrowserIntent, } from './browserSession.js';
package/dist/browser.js CHANGED
@@ -20,7 +20,8 @@ export { rewindDeterministicTargets, runDeterministicMovementCrowdSimulation, ru
20
20
  export { stepDeterministicCarry3D, stepDeterministicKcc3D } from './movement3d.js';
21
21
  export { applyDeterministicReciprocalAvoidance, cookDeterministicNavigationGraph, cookTilemapNavigationGraph, createDeterministicFlowField, findDeterministicPath, smoothDeterministicPath, stepDeterministicNavigationAgents } from './navigation.js';
22
22
  export { cookDeterministicNavmesh, cookImportedDeterministicNavmesh, findDeterministicNavmeshPath, findDeterministicNavmeshPointPath, locateDeterministicNavmeshPolygon, runDeterministicNavmeshStress, smoothDeterministicNavmeshPath, stepDeterministicNavmeshAgents, stepDeterministicNavmeshOffMeshTraversal } from './navmesh.js';
23
- export { createNetworkedSyncplayClient } from './networked-client.js';
23
+ export { createNetworkedSyncplayClient, prepareNetworkedSyncplayTransport } from './networked-client.js';
24
+ export { createSyncplayRunner } from './runner.js';
24
25
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
25
26
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
26
27
  export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
@@ -48,3 +49,6 @@ export { createSyncplaySecretEventReader, createSyncplaySecretsClient } from './
48
49
  export * from './run-room-transport.js';
49
50
  export { deriveCoarseRegion, detectCoarseRegion } from './coarse-region.js';
50
51
  export { WsMultiplayerApi } from '@series-inc/rundot-game-sdk/mp-client';
52
+ // Browser launch-intent / room-open / share composition over the RUN game API
53
+ // (RundotGameAPI comes from the SDK — syncplay may depend on the SDK).
54
+ export { openSyncplayBrowserRoom, resolveSyncplayBrowserIntent, shareSyncplayRoom, } from './browserSession.js';
@@ -0,0 +1,44 @@
1
+ import type { RundotGameAPI } from '@series-inc/rundot-game-sdk/api';
2
+ import { type SyncplayRoomOptions, type SyncplayRoomTransport } from './run-room-transport.js';
3
+ export type SyncplayBrowserIntent = {
4
+ readonly kind: 'offline';
5
+ } | {
6
+ readonly kind: 'create';
7
+ } | {
8
+ readonly kind: 'join';
9
+ readonly roomCode: string;
10
+ } | {
11
+ readonly kind: 'quick-match';
12
+ };
13
+ export type SyncplayBrowserApi = Pick<RundotGameAPI, 'app' | 'accessGate' | 'social' | 'realtime'>;
14
+ export interface ResolveSyncplayBrowserIntentOptions {
15
+ readonly override?: SyncplayBrowserIntent;
16
+ readonly search?: string;
17
+ readonly maxWaitMs?: number;
18
+ }
19
+ export interface OpenSyncplayBrowserRoomOptions extends ResolveSyncplayBrowserIntentOptions {
20
+ readonly quickMatch?: {
21
+ readonly autoRegion?: boolean;
22
+ readonly criteria?: Record<string, string | number>;
23
+ };
24
+ }
25
+ export type OpenSyncplayBrowserRoomResult = {
26
+ readonly kind: 'offline';
27
+ readonly intent: {
28
+ readonly kind: 'offline';
29
+ };
30
+ } | {
31
+ readonly kind: 'sign-in-required';
32
+ readonly intent: Exclude<SyncplayBrowserIntent, {
33
+ readonly kind: 'offline';
34
+ }>;
35
+ } | {
36
+ readonly kind: 'connected';
37
+ readonly intent: Exclude<SyncplayBrowserIntent, {
38
+ readonly kind: 'offline';
39
+ }>;
40
+ readonly transport: SyncplayRoomTransport;
41
+ };
42
+ export declare function resolveSyncplayBrowserIntent(api: SyncplayBrowserApi, options?: ResolveSyncplayBrowserIntentOptions): Promise<SyncplayBrowserIntent>;
43
+ export declare function openSyncplayBrowserRoom(api: SyncplayBrowserApi, roomOptions: SyncplayRoomOptions, options?: OpenSyncplayBrowserRoomOptions): Promise<OpenSyncplayBrowserRoomResult>;
44
+ export declare function shareSyncplayRoom(api: Pick<RundotGameAPI, 'social'>, roomCode: string): ReturnType<RundotGameAPI['social']['shareLinkAsync']>;
@@ -0,0 +1,70 @@
1
+ import { createSyncplayRoom, joinSyncplayRoomByCode, quickMatchSyncplayRoom, } from './run-room-transport.js';
2
+ function normalizeRoomCode(value) {
3
+ const code = value.trim().toUpperCase();
4
+ if (!/^[A-Z0-9]{6}$/u.test(code))
5
+ throw new Error('SYNCPLAY_ROOM_CODE_INVALID');
6
+ return code;
7
+ }
8
+ // `lenient` degrades a malformed room code (ignore it, fall through to offline)
9
+ // instead of throwing a boot-crash out of intent resolution. It is used for
10
+ // EVERY external channel — the raw URL query AND the RUN launch intent, whose
11
+ // `share`/`deeplink` kinds carry share-link and OS-deep-link params an attacker
12
+ // can craft. Only the game's explicit UI `override` is trusted and stays strict
13
+ // (a bad code there is a programming error worth surfacing).
14
+ function intentFromParams(params, lenient = false) {
15
+ if (params.room !== undefined) {
16
+ const code = params.room.trim().toUpperCase();
17
+ if (/^[A-Z0-9]{6}$/u.test(code))
18
+ return { kind: 'join', roomCode: code };
19
+ if (!lenient)
20
+ throw new Error('SYNCPLAY_ROOM_CODE_INVALID');
21
+ // lenient: ignore the malformed code and fall through to the other params
22
+ }
23
+ if (params.host === '1')
24
+ return { kind: 'create' };
25
+ if (params.quickMatch === '1')
26
+ return { kind: 'quick-match' };
27
+ return undefined;
28
+ }
29
+ function paramsFromSearch(search) {
30
+ const params = new URLSearchParams(search);
31
+ return Object.fromEntries([...params.entries()]);
32
+ }
33
+ function normalizeIntent(intent) {
34
+ return intent.kind === 'join'
35
+ ? { kind: 'join', roomCode: normalizeRoomCode(intent.roomCode) }
36
+ : intent;
37
+ }
38
+ export async function resolveSyncplayBrowserIntent(api, options = {}) {
39
+ if (options.override !== undefined)
40
+ return normalizeIntent(options.override);
41
+ const launch = await api.app.resolveLaunchIntent(options.maxWaitMs === undefined ? undefined : { maxWaitMs: options.maxWaitMs });
42
+ if (launch.kind !== 'timed_out') {
43
+ const launchIntent = intentFromParams(launch.params, true);
44
+ if (launchIntent !== undefined)
45
+ return launchIntent;
46
+ }
47
+ const search = options.search ?? (typeof globalThis.location === 'object' ? globalThis.location.search : '');
48
+ return intentFromParams(paramsFromSearch(search), true) ?? { kind: 'offline' };
49
+ }
50
+ export async function openSyncplayBrowserRoom(api, roomOptions, options = {}) {
51
+ const intent = await resolveSyncplayBrowserIntent(api, options);
52
+ if (intent.kind === 'offline')
53
+ return { kind: 'offline', intent };
54
+ if (api.accessGate.isAnonymous())
55
+ return { kind: 'sign-in-required', intent };
56
+ let transport;
57
+ if (intent.kind === 'join') {
58
+ transport = await joinSyncplayRoomByCode(api.realtime, intent.roomCode);
59
+ }
60
+ else if (intent.kind === 'create') {
61
+ transport = await createSyncplayRoom(api.realtime, roomOptions);
62
+ }
63
+ else {
64
+ transport = await quickMatchSyncplayRoom(api.realtime, { ...roomOptions, autoRegion: options.quickMatch?.autoRegion }, options.quickMatch?.criteria);
65
+ }
66
+ return { kind: 'connected', intent, transport };
67
+ }
68
+ export async function shareSyncplayRoom(api, roomCode) {
69
+ return api.social.shareLinkAsync({ shareParams: { room: normalizeRoomCode(roomCode) } });
70
+ }
package/dist/index.d.ts CHANGED
@@ -22,7 +22,8 @@ export { rewindDeterministicTargets, runDeterministicMovementCrowdSimulation, ru
22
22
  export { stepDeterministicCarry3D, stepDeterministicKcc3D } from './movement3d.js';
23
23
  export { applyDeterministicReciprocalAvoidance, cookDeterministicNavigationGraph, cookTilemapNavigationGraph, createDeterministicFlowField, findDeterministicPath, smoothDeterministicPath, stepDeterministicNavigationAgents } from './navigation.js';
24
24
  export { cookDeterministicNavmesh, cookImportedDeterministicNavmesh, findDeterministicNavmeshPath, findDeterministicNavmeshPointPath, locateDeterministicNavmeshPolygon, runDeterministicNavmeshStress, smoothDeterministicNavmeshPath, stepDeterministicNavmeshAgents, stepDeterministicNavmeshOffMeshTraversal } from './navmesh.js';
25
- export { createNetworkedSyncplayClient } from './networked-client.js';
25
+ export { createNetworkedSyncplayClient, prepareNetworkedSyncplayTransport } from './networked-client.js';
26
+ export { createSyncplayRunner } from './runner.js';
26
27
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
27
28
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
28
29
  export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
@@ -70,7 +71,8 @@ export type { DeterministicFpsAimState, DeterministicFpsInput, DeterministicKccB
70
71
  export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
71
72
  export type { CookedDeterministicNavigationGraph, DeterministicFlowField, DeterministicNavEdge, DeterministicNavigationAgent, DeterministicNavigationGraphSource, DeterministicNavigationStepOptions, DeterministicNavigationStepResult, DeterministicNavNode, DeterministicNavRegion, DeterministicPathQuery, DeterministicPathResult, DeterministicTilemapSource } from './navigation.js';
72
73
  export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, DeterministicNavmeshAgentStepResult, DeterministicNavmeshImportSource, DeterministicNavmeshLink, DeterministicNavmeshMovingAvoidanceObstacle, DeterministicNavmeshOffMeshTraversalState, DeterministicNavmeshOffMeshTraversalStep, DeterministicNavmeshPath, DeterministicNavmeshPoint, DeterministicNavmeshPolygon, DeterministicNavmeshSource } from './navmesh.js';
73
- export type { NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions } from './networked-client.js';
74
+ export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, PreparedNetworkedSyncplayTransport } from './networked-client.js';
75
+ export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
74
76
  export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
75
77
  export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
76
78
  export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D } from './physics3d-voxel.js';
package/dist/index.js CHANGED
@@ -23,7 +23,8 @@ export { rewindDeterministicTargets, runDeterministicMovementCrowdSimulation, ru
23
23
  export { stepDeterministicCarry3D, stepDeterministicKcc3D } from './movement3d.js';
24
24
  export { applyDeterministicReciprocalAvoidance, cookDeterministicNavigationGraph, cookTilemapNavigationGraph, createDeterministicFlowField, findDeterministicPath, smoothDeterministicPath, stepDeterministicNavigationAgents } from './navigation.js';
25
25
  export { cookDeterministicNavmesh, cookImportedDeterministicNavmesh, findDeterministicNavmeshPath, findDeterministicNavmeshPointPath, locateDeterministicNavmeshPolygon, runDeterministicNavmeshStress, smoothDeterministicNavmeshPath, stepDeterministicNavmeshAgents, stepDeterministicNavmeshOffMeshTraversal } from './navmesh.js';
26
- export { createNetworkedSyncplayClient } from './networked-client.js';
26
+ export { createNetworkedSyncplayClient, prepareNetworkedSyncplayTransport } from './networked-client.js';
27
+ export { createSyncplayRunner } from './runner.js';
27
28
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
28
29
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
29
30
  export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
@@ -1,6 +1,6 @@
1
1
  import type { EncodedInput } from './input-authority.js';
2
2
  import { type DeterministicSessionRole } from './session-wire.js';
3
- import { type KinetixRuntime, type KinetixRuntimeIdentity } from './runtime-session.js';
3
+ import { type KinetixRuntimeIdentity, type ProjectionCapableRuntime } from './runtime-session.js';
4
4
  import type { ReplayFile } from './types.js';
5
5
  import { type SyncplaySecretsClient } from './secret-client.js';
6
6
  import type { SyncplaySecretBrowserCrypto } from './secret-crypto.js';
@@ -21,11 +21,18 @@ export interface NetworkedClientTransport {
21
21
  onSecretMessage?(handler: (text: string) => void): void;
22
22
  readonly playerId?: string;
23
23
  }
24
- export type KinetixRuntimeFactory<State, Input, Checkpoint = unknown> = (identity: KinetixRuntimeIdentity, sessionConfigBytes: Uint8Array) => KinetixRuntime<State, Input, Checkpoint>;
25
- export interface NetworkedSyncplayClientOptions<State, Input, Checkpoint = unknown> {
24
+ export type KinetixRuntimeFactory<State, Input, Checkpoint = unknown, Projection = State> = (identity: KinetixRuntimeIdentity, sessionConfigBytes: Uint8Array) => ProjectionCapableRuntime<State, Input, Checkpoint, Projection>;
25
+ export interface NetworkedSyncplayClientOptions<State, Input, Checkpoint = unknown, Projection = State> {
26
26
  readonly transport: NetworkedClientTransport;
27
- readonly runtimeFactory: KinetixRuntimeFactory<State, Input, Checkpoint>;
27
+ readonly runtimeFactory: KinetixRuntimeFactory<State, Input, Checkpoint, Projection>;
28
28
  readonly defaultInput: Input;
29
+ /**
30
+ * Enables a bounded queue of authority-confirmed presentation frames drained
31
+ * via {@link NetworkedSyncplayClient.drainConfirmedPresentationFrames}. Absent
32
+ * means no queue and preserves existing client cost. Must be a positive safe
33
+ * integer.
34
+ */
35
+ readonly confirmedPresentationBufferSize?: number;
29
36
  readonly snapshotBufferSize?: number;
30
37
  readonly checksumIntervalFrames?: number;
31
38
  /** This client's own input for a tick (game-defined). */
@@ -107,7 +114,29 @@ export interface NetworkedSyncplayClientNetStats {
107
114
  readonly maxRollbackDepth: number;
108
115
  }
109
116
  export type NetworkedSyncplaySlotPresence = 'human' | 'substituted' | 'empty' | 'unknown';
110
- export interface NetworkedSyncplayClient {
117
+ export interface ConfirmedSyncplayPresentationFrame<Projection> {
118
+ readonly frame: number;
119
+ readonly projection: Readonly<Projection>;
120
+ }
121
+ export interface NetworkedSyncplaySessionDescriptor {
122
+ readonly slot: number;
123
+ readonly playerCount: number;
124
+ readonly seed: number;
125
+ readonly tickRateHz: number;
126
+ readonly hardToleranceTicks: number;
127
+ readonly role: DeterministicSessionRole;
128
+ readonly runtimeIdentity: KinetixRuntimeIdentity;
129
+ readonly sessionConfigBytes: Uint8Array;
130
+ readonly sessionConfigDigest: string;
131
+ }
132
+ export interface PreparedNetworkedSyncplayTransport {
133
+ readonly descriptor: NetworkedSyncplaySessionDescriptor;
134
+ readonly transport: NetworkedClientTransport;
135
+ }
136
+ export interface NetworkedSyncplayPreparationOptions {
137
+ readonly signal?: AbortSignal;
138
+ }
139
+ export interface NetworkedSyncplayClient<Projection = unknown> {
111
140
  readonly slot: number;
112
141
  /** 'player' (holds a slot) or 'spectator' (read-only follower, slot -1). From session-start. */
113
142
  readonly role: DeterministicSessionRole;
@@ -138,5 +167,21 @@ export interface NetworkedSyncplayClient {
138
167
  /** Confirmed-state checksum at `frame` (must be <= appliedThrough + 1). */
139
168
  confirmedStateChecksum(frame: number): string;
140
169
  exportReplay(): ReplayFile;
170
+ readonly currentFrame: number;
171
+ readonly renderAlpha: number;
172
+ readonly caughtUp: boolean;
173
+ getPresentationFrame(): Readonly<Projection>;
174
+ drainConfirmedPresentationFrames(): readonly ConfirmedSyncplayPresentationFrame<Projection>[];
175
+ beginPresentationCatchUp(): void;
176
+ dispose(): void;
141
177
  }
142
- export declare function createNetworkedSyncplayClient<State, Input, Checkpoint = unknown>(options: NetworkedSyncplayClientOptions<State, Input, Checkpoint>): NetworkedSyncplayClient;
178
+ /**
179
+ * Attaches to the ordered transport, awaits and validates the first
180
+ * `session-start`, exposes an immutable descriptor with detached session-config
181
+ * bytes, buffers subsequent public messages (same 8,192-message ceiling as the
182
+ * SDK transport), awaits `prepare`, then returns a replaying transport for
183
+ * {@link createNetworkedSyncplayClient}. Rejects on abort, terminal
184
+ * session-close/error, duplicate handler attachment, or overflow.
185
+ */
186
+ export declare function prepareNetworkedSyncplayTransport(transport: NetworkedClientTransport, prepare: (descriptor: NetworkedSyncplaySessionDescriptor, signal: AbortSignal) => Promise<void>, options?: NetworkedSyncplayPreparationOptions): Promise<PreparedNetworkedSyncplayTransport>;
187
+ export declare function createNetworkedSyncplayClient<State, Input, Checkpoint = unknown, Projection = State>(options: NetworkedSyncplayClientOptions<State, Input, Checkpoint, Projection>): NetworkedSyncplayClient<Projection>;