@series-inc/rundot-syncplay 5.25.0-beta.3 → 5.25.0-beta.5
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 +99 -0
- package/dist/browser.d.ts +6 -2
- package/dist/browser.js +5 -1
- package/dist/browserSession.d.ts +44 -0
- package/dist/browserSession.js +70 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/networked-client.d.ts +51 -6
- package/dist/networked-client.js +266 -23
- package/dist/replay.d.ts +8 -7
- package/dist/runner.d.ts +85 -0
- package/dist/runner.js +314 -0
- package/dist/synctest.d.ts +5 -5
- package/dist/testing-network.d.ts +24 -0
- package/dist/testing-network.js +92 -0
- package/dist/testing.d.ts +34 -1
- package/dist/testing.js +88 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -110,3 +110,102 @@ 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
|
+
**Incremental adoption (mature games).** A game with its own stateful renderer,
|
|
161
|
+
input model, and connection UI can swap only its lifecycle orchestration onto the
|
|
162
|
+
runner without rewriting those layers:
|
|
163
|
+
|
|
164
|
+
- Keep your own presentation singleton — use an identity `project: (frame) => frame`
|
|
165
|
+
and feed your renderer from the `subscribe` snapshot (`renderState`, `renderAlpha`)
|
|
166
|
+
instead of `getRenderState()`.
|
|
167
|
+
- Supply `localInputForTick: (slot, frame) => Input` when your local input is a
|
|
168
|
+
per-tick pulse/edge that must be consumed and cleared each frame; the runner then
|
|
169
|
+
reads it (offline local slot and networked prediction) instead of the persistent
|
|
170
|
+
`applyInput(...)` value.
|
|
171
|
+
- Drive a room badge from the snapshot's `ready`, `appliedThrough`, `predictedThrough`,
|
|
172
|
+
`localSlot`, and `status` — no reach into the underlying client (e.g. `appliedThrough < 0`
|
|
173
|
+
means "waiting for peers"; `predictedThrough - appliedThrough` is the catch-up depth).
|
|
174
|
+
|
|
175
|
+
Direct `NetworkedSyncplayClient` users get the same projection surface:
|
|
176
|
+
`getPresentationFrame()`, `renderAlpha`, `currentFrame`, `caughtUp`, and a
|
|
177
|
+
bounded confirmed-frame queue via the optional `confirmedPresentationBufferSize`
|
|
178
|
+
(absent = no queue, preserving existing cost) drained with
|
|
179
|
+
`drainConfirmedPresentationFrames()`. Call `beginPresentationCatchUp()` from your
|
|
180
|
+
transport's reconnect signal so replayed history never fills the queue.
|
|
181
|
+
`prepareNetworkedSyncplayTransport(transport, prepare, { signal })` buffers the
|
|
182
|
+
ordered `session-start`/history traffic (same 8,192-message ceiling as the SDK
|
|
183
|
+
transport), exposes a frozen descriptor with `levelEntryId + digest + length`,
|
|
184
|
+
awaits your async preparation, then replays every buffered message once.
|
|
185
|
+
|
|
186
|
+
**Capacity and late-join budgets.** Snapshot transfer defaults are **256 KiB**
|
|
187
|
+
chunks and a **4 MiB** total ceiling; the immutable level payload is fetched and
|
|
188
|
+
verified separately and never rides a snapshot. `maxPredictionTicks` is a
|
|
189
|
+
rollback/latency budget, not a player-count limit. 128 slots is the tested
|
|
190
|
+
protocol/load ceiling, not a 60 Hz performance guarantee for an arbitrary game.
|
|
191
|
+
|
|
192
|
+
## Supported correctness kit
|
|
193
|
+
|
|
194
|
+
`@series-inc/rundot-syncplay/testing` exposes exactly six supported functions —
|
|
195
|
+
no browser or runtime APIs leak through it:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import {
|
|
199
|
+
assertDeterministic,
|
|
200
|
+
assertLateJoinHydration,
|
|
201
|
+
assertReplayVerifies,
|
|
202
|
+
assertTwoClientConvergence,
|
|
203
|
+
} from '@series-inc/rundot-syncplay/testing'
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`assertDeterministic` and `assertReplayVerifies` run the straight-run /
|
|
207
|
+
restore-resim / hydration / replay cert; `assertTwoClientConvergence` and
|
|
208
|
+
`assertLateJoinHydration` run the **real** authority room and networked client
|
|
209
|
+
over a deterministic simulated network (`createSimulatedSyncplayMatch`,
|
|
210
|
+
`runSyncplaySynctest`). Failures name the phase, frame, and disagreeing
|
|
211
|
+
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
|
|
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) =>
|
|
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
|
|
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
|
-
|
|
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>;
|