@series-inc/rundot-syncplay 5.24.0-beta.0 → 5.24.0-beta.2
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 +42 -14
- package/dist/authority-room.d.ts +17 -0
- package/dist/authority-room.js +48 -1
- package/dist/browser.d.ts +14 -3
- package/dist/browser.js +7 -1
- package/dist/cjs/authority-room.js +48 -1
- package/dist/cjs/browser.js +23 -2
- package/dist/cjs/cosmetic-physics3d.js +201 -0
- package/dist/cjs/creator.js +26 -1
- package/dist/cjs/engine-complete.js +10 -8
- package/dist/cjs/engine-parity-matrix.js +11 -2
- package/dist/cjs/engine-parity.js +6 -3
- package/dist/cjs/index.js +28 -2
- package/dist/cjs/input-authority.js +66 -2
- package/dist/cjs/local-authority-server-runner.js +3 -1
- package/dist/cjs/local-authority-wire.js +20 -1
- package/dist/cjs/match-log.js +27 -0
- package/dist/cjs/math.js +30 -0
- package/dist/cjs/networked-client.js +94 -13
- package/dist/cjs/node.js +19 -1
- package/dist/cjs/physics-cert.js +1 -1
- package/dist/cjs/physics3d-destruction-cert.js +279 -0
- package/dist/cjs/physics3d-heightfield.js +51 -0
- package/dist/cjs/physics3d-joints.js +92 -18
- package/dist/cjs/physics3d-mass.js +131 -0
- package/dist/cjs/physics3d-shared.js +5 -1
- package/dist/cjs/physics3d-solver.js +204 -51
- package/dist/cjs/physics3d-toi.js +23 -0
- package/dist/cjs/physics3d-voxel.js +94 -0
- package/dist/cjs/physics3d.js +327 -27
- package/dist/cjs/replay.js +25 -1
- package/dist/cjs/sdk-offline.js +9 -1
- package/dist/cjs/sdk-session.js +9 -0
- package/dist/cjs/secret-authority.js +647 -0
- package/dist/cjs/secret-client.js +615 -0
- package/dist/cjs/secret-command-classification.js +8 -0
- package/dist/cjs/secret-config.js +211 -0
- package/dist/cjs/secret-crypto.js +208 -0
- package/dist/cjs/secret-protocol.js +238 -0
- package/dist/cjs/session-build.js +7 -4
- package/dist/cjs/session-wire.js +12 -6
- package/dist/cjs/tools/static-checker.js +61 -13
- package/dist/cli.js +77 -10
- package/dist/cosmetic-physics3d.d.ts +41 -0
- package/dist/cosmetic-physics3d.js +196 -0
- package/dist/creator.d.ts +8 -0
- package/dist/creator.js +7 -0
- package/dist/deterministic-primitives.d.ts +1 -0
- package/dist/engine-complete.js +10 -8
- package/dist/engine-parity-matrix.js +11 -2
- package/dist/engine-parity.js +6 -3
- package/dist/index.d.ts +17 -2
- package/dist/index.js +9 -1
- package/dist/input-authority.d.ts +6 -0
- package/dist/input-authority.js +67 -3
- package/dist/local-authority-server-runner.d.ts +1 -0
- package/dist/local-authority-server-runner.js +2 -1
- package/dist/local-authority-wire.js +20 -1
- package/dist/match-log.d.ts +4 -1
- package/dist/match-log.js +27 -0
- package/dist/math.d.ts +4 -0
- package/dist/math.js +30 -0
- package/dist/networked-client.d.ts +17 -0
- package/dist/networked-client.js +94 -13
- package/dist/node.d.ts +5 -0
- package/dist/node.js +4 -0
- package/dist/physics-cert.js +1 -1
- package/dist/physics3d-destruction-cert.d.ts +35 -0
- package/dist/physics3d-destruction-cert.js +276 -0
- package/dist/physics3d-heightfield.d.ts +17 -0
- package/dist/physics3d-heightfield.js +47 -0
- package/dist/physics3d-joints.d.ts +6 -1
- package/dist/physics3d-joints.js +93 -19
- package/dist/physics3d-mass.d.ts +11 -0
- package/dist/physics3d-mass.js +126 -0
- package/dist/physics3d-shared.d.ts +1 -0
- package/dist/physics3d-shared.js +4 -1
- package/dist/physics3d-solver.d.ts +41 -4
- package/dist/physics3d-solver.js +202 -51
- package/dist/physics3d-toi.d.ts +9 -0
- package/dist/physics3d-toi.js +22 -0
- package/dist/physics3d-voxel.d.ts +20 -0
- package/dist/physics3d-voxel.js +91 -0
- package/dist/physics3d.d.ts +38 -3
- package/dist/physics3d.js +329 -30
- package/dist/replay.d.ts +5 -0
- package/dist/replay.js +25 -1
- package/dist/reports.d.ts +6 -1
- package/dist/sdk-offline.d.ts +4 -0
- package/dist/sdk-offline.js +9 -1
- package/dist/sdk-session.d.ts +6 -0
- package/dist/sdk-session.js +9 -0
- package/dist/secret-authority.d.ts +90 -0
- package/dist/secret-authority.js +643 -0
- package/dist/secret-client.d.ts +89 -0
- package/dist/secret-client.js +611 -0
- package/dist/secret-command-classification.d.ts +1 -0
- package/dist/secret-command-classification.js +5 -0
- package/dist/secret-config.d.ts +45 -0
- package/dist/secret-config.js +206 -0
- package/dist/secret-crypto.d.ts +29 -0
- package/dist/secret-crypto.js +190 -0
- package/dist/secret-protocol.d.ts +181 -0
- package/dist/secret-protocol.js +230 -0
- package/dist/session-build.d.ts +16 -0
- package/dist/session-build.js +7 -4
- package/dist/session-wire.js +12 -6
- package/dist/tools/static-checker.d.ts +11 -0
- package/dist/tools/static-checker.js +60 -13
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# RUN Syncplay
|
|
2
2
|
|
|
3
|
-
Offline-first deterministic multiplayer engine for RUN.
|
|
3
|
+
Offline-first deterministic multiplayer engine for RUN.world. You author a
|
|
4
4
|
**deterministic** game (`defineSyncplayGame`, `DeterministicSystem`,
|
|
5
5
|
`DeterministicRandom`) and Syncplay runs it — input authority, prediction, and
|
|
6
6
|
rollback — with no game-specific server logic.
|
|
@@ -8,18 +8,29 @@ rollback — with no game-specific server logic.
|
|
|
8
8
|
This package is intentionally standalone. It does not depend on Venus MP,
|
|
9
9
|
Cloud Run, React Three Fiber, Rapier, Firebase, or the SDK bridge.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Public and private projections
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
The deterministic session remains a shared public projection: every client
|
|
14
|
+
applies the same confirmed inputs and authority commands. Optional secret
|
|
15
|
+
systems add a separate authority-owned projection for simultaneous choices,
|
|
16
|
+
cryptographic random draws, decks, bags, and roles. Private values go only to
|
|
17
|
+
their owners; signed public consequences join exactly one confirmed frame and
|
|
18
|
+
therefore survive rollback, replay, reconnect, and match-log verification.
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
The RUN room authority is trusted and can see secret values. SHA-256
|
|
21
|
+
commitments, hash chains, Merkle proofs, and Ed25519 receipts prevent peers or
|
|
22
|
+
tampered logs from forging outcomes; they do not hide a player's own values
|
|
23
|
+
from that player's browser or make the authority zero-knowledge. Public donated
|
|
24
|
+
snapshots remain secret-free, while server-only secret state restores against a
|
|
25
|
+
cryptographic configuration hash.
|
|
26
|
+
|
|
27
|
+
## BETA Status
|
|
28
|
+
|
|
29
|
+
The standalone engine, certification CLI, curated public entrypoints, SDK bridge,
|
|
30
|
+
generic input authority, replay verification, and hosted multiplayer path are in
|
|
31
|
+
active development. Syncplay has not gone live. Package publication, production
|
|
32
|
+
rollout, and 1.0 graduation are separate release decisions; this build remains
|
|
33
|
+
explicitly BETA.
|
|
23
34
|
|
|
24
35
|
## State Ownership Contract
|
|
25
36
|
|
|
@@ -171,9 +182,11 @@ Implemented:
|
|
|
171
182
|
tests, `build:sdk`, and built `dist` import checks.
|
|
172
183
|
- Curated creator subpath export at
|
|
173
184
|
`@series-inc/rundot-game-sdk/syncplay/creator` for
|
|
174
|
-
browser-safe offline runtime, descriptors, commands, schema artifacts,
|
|
175
|
-
replay verification
|
|
176
|
-
|
|
185
|
+
browser-safe offline runtime, descriptors, commands, schema artifacts,
|
|
186
|
+
replay verification, and deterministic 3D physics (world create/step/edit,
|
|
187
|
+
voxel-chunk collider cooking, impulses, queries, vehicles) without Node
|
|
188
|
+
tooling, local servers, certification labs, render adapters, or engine
|
|
189
|
+
internals.
|
|
177
190
|
- Manifest-driven H5 creator entrypoint certification that requires game code
|
|
178
191
|
to import the safe `/creator` runtime surface and rejects broad SDK, Node,
|
|
179
192
|
tooling, lab, certification, r3f, and internal runtime imports before release
|
|
@@ -305,6 +318,11 @@ Implemented:
|
|
|
305
318
|
`applyDeterministicImpulse3D` / `setDeterministicBodyVelocity3D`; contact
|
|
306
319
|
events carry `normalImpulse`. The contact/joint caches live in the world and
|
|
307
320
|
are therefore checksummed and rollback-safe.
|
|
321
|
+
- **Render-only cosmetic 3D physics.** `createDeterministicCosmeticPhysicsWorld3D`
|
|
322
|
+
accepts dynamic cosmetic bodies plus static/kinematic authoritative proxies
|
|
323
|
+
and resolves contacts against the cosmetics only. Proxy state and impulses
|
|
324
|
+
are never written back, and the cosmetic layer has no checksum or participation
|
|
325
|
+
in Syncplay state, snapshots, replays, or network traffic.
|
|
308
326
|
- **Deterministic raycast-wheel vehicles.** `stepDeterministicVehicle3D` drives
|
|
309
327
|
a single dynamic chassis with N suspension raycasts (wheels are rays, not
|
|
310
328
|
bodies — Bullet/Godot raycast-vehicle lineage): spring-damper suspension,
|
|
@@ -557,6 +575,16 @@ remediation text. Valid suppressions must use
|
|
|
557
575
|
`// rdm-ignore-next-line <rule-id>: <reason>` and are reported with file, line,
|
|
558
576
|
rule, and reason; invalid suppressions are errors.
|
|
559
577
|
|
|
578
|
+
`static:determinism` with explicit paths (`--entry`, `--paths`) follows the
|
|
579
|
+
import graph transitively by default (`--no-follow-imports` opts out); the
|
|
580
|
+
JSON artifact records `requestedPaths` (CLI inputs), `checkedPaths` (files
|
|
581
|
+
actually checked, post-traversal), `followImports`, and `unresolvedImports`
|
|
582
|
+
(relative imports that resolve to no file — unchecked holes). Bare package
|
|
583
|
+
specifiers are never followed; if your simulation uses tsconfig path aliases,
|
|
584
|
+
pass `--project tsconfig.json` so aliased imports resolve and get checked —
|
|
585
|
+
without it they are skipped like package imports. Package self-certification
|
|
586
|
+
(no explicit paths) checks its curated source list without following imports.
|
|
587
|
+
|
|
560
588
|
The checker covers host random/time, timers, async simulation work, I/O,
|
|
561
589
|
unordered object iteration, random sort comparators, render-object mutation,
|
|
562
590
|
uncertified physics stepping, and descriptor-declared state-field access.
|
package/dist/authority-room.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type ConfirmedInputFrame, type EncodedInput } from './input-authority.js';
|
|
2
2
|
import type { SyncplayMatchLog } from './match-log.js';
|
|
3
|
+
import { type SyncplaySecretAuthoritySnapshot } from './secret-authority.js';
|
|
4
|
+
import type { SyncplaySecretAuthorityCrypto } from './secret-crypto.js';
|
|
5
|
+
import type { SyncplaySecretSystemsConfig } from './secret-config.js';
|
|
3
6
|
/**
|
|
4
7
|
* Transport-agnostic, game-agnostic deterministic authority room (§6 M2).
|
|
5
8
|
*
|
|
@@ -63,6 +66,12 @@ export interface DeterministicAuthorityRoomConfig {
|
|
|
63
66
|
readonly snapshotCadenceTicks?: number;
|
|
64
67
|
/** Abandon a donor transfer that has not completed after this many ticks. Default 600. */
|
|
65
68
|
readonly transferTimeoutTicks?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Snapshot transfer ceiling in bytes for this room's collector (default
|
|
71
|
+
* DEFAULT_MAX_SNAPSHOT_BYTES). Offers above it are rejected with
|
|
72
|
+
* `snapshot-too-large`; keep it equal to the clients' configured ceiling.
|
|
73
|
+
*/
|
|
74
|
+
readonly maxSnapshotBytes?: number;
|
|
66
75
|
/** Snapshot acquisition telemetry: stored snapshots and rejected/abandoned transfers. */
|
|
67
76
|
readonly onSnapshotEvent?: (event: AuthorityRoomSnapshotEvent) => void;
|
|
68
77
|
/**
|
|
@@ -89,6 +98,10 @@ export interface DeterministicAuthorityRoomConfig {
|
|
|
89
98
|
* MUST call `resyncSeatedPlayers()` so surviving clients re-seat.
|
|
90
99
|
*/
|
|
91
100
|
readonly restoreFrom?: DeterministicAuthorityRoomRestoreState;
|
|
101
|
+
readonly authorityId?: string;
|
|
102
|
+
readonly secrets?: SyncplaySecretSystemsConfig;
|
|
103
|
+
readonly secretCrypto?: SyncplaySecretAuthorityCrypto;
|
|
104
|
+
readonly secretMsgType?: string;
|
|
92
105
|
}
|
|
93
106
|
export interface DeterministicAuthorityRoomRestoreState {
|
|
94
107
|
readonly now: number;
|
|
@@ -98,6 +111,7 @@ export interface DeterministicAuthorityRoomRestoreState {
|
|
|
98
111
|
readonly tail: readonly ConfirmedInputFrame[];
|
|
99
112
|
readonly snapshotBytes?: string;
|
|
100
113
|
readonly schemaHash?: string;
|
|
114
|
+
readonly secretState?: SyncplaySecretAuthoritySnapshot;
|
|
101
115
|
}
|
|
102
116
|
export interface AuthorityRoomCatchUpEvent {
|
|
103
117
|
readonly playerId: string;
|
|
@@ -178,6 +192,7 @@ export interface DeterministicAuthorityRoom {
|
|
|
178
192
|
/** Seat a read-only spectator (G11): role 'spectator', no slot, full catch-up + live follow. */
|
|
179
193
|
onSpectatorJoin(playerId: string): boolean;
|
|
180
194
|
onPlayerMessage(playerId: string, envelope: string): void;
|
|
195
|
+
onPlayerSecretMessage(playerId: string, envelope: string): void;
|
|
181
196
|
onPlayerLeave(playerId: string): void;
|
|
182
197
|
/**
|
|
183
198
|
* A player's transport re-attached inside the host's grace window (socket
|
|
@@ -213,5 +228,7 @@ export interface DeterministicAuthorityRoom {
|
|
|
213
228
|
* `preserveFullHistory`, or the match outlived `historyRetentionTicks`).
|
|
214
229
|
*/
|
|
215
230
|
matchLog(): SyncplayMatchLog | undefined;
|
|
231
|
+
/** Server-only secret state. Never include this value in public snapshots or match logs. */
|
|
232
|
+
secretSnapshot(): SyncplaySecretAuthoritySnapshot | undefined;
|
|
216
233
|
}
|
|
217
234
|
export declare function createDeterministicAuthorityRoom(transport: AuthorityRoomTransport, config: DeterministicAuthorityRoomConfig): DeterministicAuthorityRoom;
|
package/dist/authority-room.js
CHANGED
|
@@ -4,7 +4,9 @@ import { createInputAuthority } from './input-authority.js';
|
|
|
4
4
|
import { createDeterministicLocalAuthoritySnapshotChunkEnvelope, createDeterministicLocalAuthoritySnapshotCompleteEnvelope, createDeterministicLocalAuthoritySnapshotOfferEnvelope, createDeterministicLocalAuthoritySnapshotRequestEnvelope, decodeDeterministicLocalAuthorityWireEnvelope, } from './local-authority-wire.js';
|
|
5
5
|
import { DEFAULT_SNAPSHOT_CHUNK_SIZE, createSnapshotTransferCollector, createSnapshotTransferFromBytes, decodeDeterministicStateSnapshot, deterministicRoomDescriptorHash, } from './session-snapshot.js';
|
|
6
6
|
import { decodeDeterministicSessionMessage, encodeDeterministicSessionMessage } from './session-wire.js';
|
|
7
|
+
import { createSyncplaySecretAuthority, } from './secret-authority.js';
|
|
7
8
|
const DEFAULT_SESSION_MSG_TYPE = 'rdm';
|
|
9
|
+
const DEFAULT_SECRET_MSG_TYPE = 'rdm-secret';
|
|
8
10
|
/** One hour at 60 Hz — see DeterministicAuthorityRoomConfig.historyRetentionTicks. */
|
|
9
11
|
const DEFAULT_HISTORY_RETENTION_TICKS = 216_000;
|
|
10
12
|
/** Frames per history-replay unicast. Bounds message size during catch-up. */
|
|
@@ -16,6 +18,9 @@ const DEFAULT_TRANSFER_TIMEOUT_TICKS = 600;
|
|
|
16
18
|
export function createDeterministicAuthorityRoom(transport, config) {
|
|
17
19
|
const sessionMsgType = config.sessionMsgType ?? DEFAULT_SESSION_MSG_TYPE;
|
|
18
20
|
const historyRetentionTicks = config.historyRetentionTicks ?? DEFAULT_HISTORY_RETENTION_TICKS;
|
|
21
|
+
if (config.maxSnapshotBytes !== undefined && (!Number.isInteger(config.maxSnapshotBytes) || config.maxSnapshotBytes < 1)) {
|
|
22
|
+
throw new Error(`createDeterministicAuthorityRoom: maxSnapshotBytes must be a positive integer, received ${config.maxSnapshotBytes}`);
|
|
23
|
+
}
|
|
19
24
|
const authority = createInputAuthority({
|
|
20
25
|
playerCount: config.playerCount,
|
|
21
26
|
hardToleranceTicks: config.hardToleranceTicks,
|
|
@@ -48,6 +53,18 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
48
53
|
// colliding with a still-present player and orphaning the vacated slot.
|
|
49
54
|
let assignedSlots = 0;
|
|
50
55
|
let now = -1;
|
|
56
|
+
const secretAuthority = config.secrets === undefined
|
|
57
|
+
? undefined
|
|
58
|
+
: createSyncplaySecretAuthority({
|
|
59
|
+
authorityId: requireSecretAuthorityId(config.authorityId),
|
|
60
|
+
playerCount: config.playerCount,
|
|
61
|
+
config: config.secrets,
|
|
62
|
+
crypto: requireSecretCrypto(config.secretCrypto),
|
|
63
|
+
sendPrivate(playerId, encoded) {
|
|
64
|
+
transport.sendTo(playerId, config.secretMsgType ?? DEFAULT_SECRET_MSG_TYPE, encoded);
|
|
65
|
+
},
|
|
66
|
+
...(config.restoreFrom?.secretState === undefined ? {} : { restoreFrom: config.restoreFrom.secretState }),
|
|
67
|
+
});
|
|
51
68
|
// M5.5 snapshot acquisition state. The authority stores at most one validated
|
|
52
69
|
// snapshot and runs at most one donor transfer at a time.
|
|
53
70
|
const snapshotCadenceTicks = config.snapshotCadenceTicks ?? DEFAULT_SNAPSHOT_CADENCE_TICKS;
|
|
@@ -227,6 +244,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
227
244
|
const plan = catchUpPlan();
|
|
228
245
|
invariant(plan !== 'unavailable', 'a restored room must have a catch-up plan for seated players', 'authority-room.restore-catchup');
|
|
229
246
|
sendSessionStart(playerId, slot);
|
|
247
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
230
248
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
231
249
|
}
|
|
232
250
|
}
|
|
@@ -246,6 +264,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
246
264
|
}
|
|
247
265
|
slotByPlayer.set(playerId, formerSlot);
|
|
248
266
|
sendSessionStart(playerId, formerSlot);
|
|
267
|
+
secretAuthority?.onPlayerReconnected(playerId, formerSlot);
|
|
249
268
|
sendCatchUpTo(playerId, rejoinPlan, 'rejoin');
|
|
250
269
|
return { accepted: true, slot: formerSlot, rejoined: true };
|
|
251
270
|
}
|
|
@@ -264,6 +283,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
264
283
|
slotByPlayer.set(playerId, slot);
|
|
265
284
|
everSlotByPlayer.set(playerId, slot);
|
|
266
285
|
sendSessionStart(playerId, slot);
|
|
286
|
+
secretAuthority?.onPlayerJoined(playerId, slot);
|
|
267
287
|
sendCatchUpTo(playerId, plan, 'join');
|
|
268
288
|
return { accepted: true, slot };
|
|
269
289
|
}
|
|
@@ -283,6 +303,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
283
303
|
return false;
|
|
284
304
|
spectators.add(playerId);
|
|
285
305
|
sendSessionStart(playerId, -1, 'spectator');
|
|
306
|
+
secretAuthority?.onSpectatorJoined(playerId);
|
|
286
307
|
sendCatchUpTo(playerId, plan, 'join');
|
|
287
308
|
return true;
|
|
288
309
|
}
|
|
@@ -309,8 +330,14 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
309
330
|
// client that fell behind the snapshot tick, so the stored snapshot rides
|
|
310
331
|
// along — the client applies it only when it is ahead of its own state.
|
|
311
332
|
const reconnectPlan = !historyReachesTickZero() && storedSnapshot !== undefined ? 'snapshot' : 'history';
|
|
333
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
312
334
|
sendCatchUpTo(playerId, reconnectPlan, 'reconnect');
|
|
313
335
|
}
|
|
336
|
+
function onPlayerSecretMessage(playerId, envelope) {
|
|
337
|
+
if (secretAuthority === undefined)
|
|
338
|
+
return;
|
|
339
|
+
secretAuthority.onPlayerRequest(playerId, slotByPlayer.get(playerId) ?? -1, envelope);
|
|
340
|
+
}
|
|
314
341
|
function onPlayerMessage(playerId, envelope) {
|
|
315
342
|
// Fail closed: unknown/malformed messages are dropped. Snapshot transfer
|
|
316
343
|
// envelopes are the OTHER frozen wire family (schemaVersion vs v field) —
|
|
@@ -516,7 +543,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
516
543
|
donorId,
|
|
517
544
|
minFrame: tick + 1,
|
|
518
545
|
startedAt: now,
|
|
519
|
-
collector: createSnapshotTransferCollector(),
|
|
546
|
+
collector: createSnapshotTransferCollector(config.maxSnapshotBytes === undefined ? {} : { maxSnapshotBytes: config.maxSnapshotBytes }),
|
|
520
547
|
isRetry,
|
|
521
548
|
};
|
|
522
549
|
transport.sendTo(donorId, sessionMsgType, createDeterministicLocalAuthoritySnapshotRequestEnvelope(nextWireSequence(), { descriptorHash: roomDescriptorHash }, tick + 1));
|
|
@@ -571,7 +598,10 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
571
598
|
// Fixed roster for v1 (late-join/reconnect is M5). Dropping the mapping stops accepting the
|
|
572
599
|
// departed player's input; their slot stays reserved because `assignedSlots` never decreases,
|
|
573
600
|
// so the authority substitutes for it rather than handing the number to a new joiner.
|
|
601
|
+
const slot = slotByPlayer.get(playerId);
|
|
574
602
|
slotByPlayer.delete(playerId);
|
|
603
|
+
if (slot !== undefined)
|
|
604
|
+
secretAuthority?.onPlayerLeft(playerId, slot);
|
|
575
605
|
if (pendingTransfer?.donorId === playerId) {
|
|
576
606
|
failPendingTransfer('snapshot-donor-left');
|
|
577
607
|
}
|
|
@@ -586,6 +616,10 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
586
616
|
return;
|
|
587
617
|
}
|
|
588
618
|
now += 1;
|
|
619
|
+
authority.recordOccupiedSlots(now, [...slotByPlayer.values()]);
|
|
620
|
+
for (const command of secretAuthority?.onTick(now, [...slotByPlayer.values()]) ?? []) {
|
|
621
|
+
authority.queueCommand(now, command);
|
|
622
|
+
}
|
|
589
623
|
authority.advance(now);
|
|
590
624
|
maintainSnapshotAcquisition();
|
|
591
625
|
if (authority.confirmedThrough < 0) {
|
|
@@ -636,6 +670,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
636
670
|
onPlayerJoin,
|
|
637
671
|
onSpectatorJoin,
|
|
638
672
|
onPlayerMessage,
|
|
673
|
+
onPlayerSecretMessage,
|
|
639
674
|
onPlayerLeave,
|
|
640
675
|
onPlayerReconnected,
|
|
641
676
|
onTick,
|
|
@@ -672,6 +707,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
672
707
|
if (plan === 'unavailable')
|
|
673
708
|
return false;
|
|
674
709
|
sendSessionStart(playerId, slot);
|
|
710
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
675
711
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
676
712
|
return true;
|
|
677
713
|
},
|
|
@@ -697,5 +733,16 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
697
733
|
frames: authority.confirmedFrameRange(0, authority.confirmedThrough),
|
|
698
734
|
};
|
|
699
735
|
},
|
|
736
|
+
secretSnapshot() {
|
|
737
|
+
return secretAuthority?.snapshot();
|
|
738
|
+
},
|
|
700
739
|
};
|
|
701
740
|
}
|
|
741
|
+
function requireSecretAuthorityId(authorityId) {
|
|
742
|
+
invariant(typeof authorityId === 'string' && authorityId.length > 0, 'secret authorityId is required', 'authority-room.secret-authority-id');
|
|
743
|
+
return authorityId;
|
|
744
|
+
}
|
|
745
|
+
function requireSecretCrypto(crypto) {
|
|
746
|
+
invariant(crypto !== undefined, 'secret signing is unavailable', 'authority-room.secret-signing');
|
|
747
|
+
return crypto;
|
|
748
|
+
}
|
package/dist/browser.d.ts
CHANGED
|
@@ -29,7 +29,9 @@ export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js
|
|
|
29
29
|
export { createOfflineSession } from './offline-session.js';
|
|
30
30
|
export { createPauseController } from './pause-resume.js';
|
|
31
31
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
32
|
-
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
32
|
+
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
33
|
+
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
34
|
+
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
33
35
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
34
36
|
export { createDeterministicR3fRenderAdapter as createDeterministicR3fAdapter, createDeterministicR3fRenderAdapter, interpolateDeterministicRenderNumber, interpolateDeterministicRenderVec2 } from './r3f-render-adapter.js';
|
|
35
37
|
export { verifyReplayWithDescriptor as verifyDeterministicReplay, verifyReplay } from './replay.js';
|
|
@@ -72,10 +74,12 @@ export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, Determinist
|
|
|
72
74
|
export type { DeterministicAuthorityCertificationArtifact, DeterministicAuthorityCertificationVerification, DeterministicAuthorityChecksums, SyncplayClient, SyncplayClientConnection, SyncplayClientRole, SyncplayCommand, SyncplayCommandPollOptions, SyncplayCommandSubmitResult, SyncplayInput, SyncplayLifecycleResult, SyncplaySubmitResult } from './multiplayer-service.js';
|
|
73
75
|
export type { CookedDeterministicNavigationGraph, DeterministicFlowField, DeterministicNavEdge, DeterministicNavigationAgent, DeterministicNavigationGraphSource, DeterministicNavigationStepOptions, DeterministicNavigationStepResult, DeterministicNavNode, DeterministicNavRegion, DeterministicPathQuery, DeterministicPathResult, DeterministicTilemapSource } from './navigation.js';
|
|
74
76
|
export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, DeterministicNavmeshAgentStepResult, DeterministicNavmeshImportSource, DeterministicNavmeshLink, DeterministicNavmeshMovingAvoidanceObstacle, DeterministicNavmeshOffMeshTraversalState, DeterministicNavmeshOffMeshTraversalStep, DeterministicNavmeshPath, DeterministicNavmeshPoint, DeterministicNavmeshPolygon, DeterministicNavmeshSource } from './navmesh.js';
|
|
75
|
-
export type { NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions } from './networked-client.js';
|
|
77
|
+
export type { NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplaySlotPresence } from './networked-client.js';
|
|
76
78
|
export type { DeterministicPauseController, DeterministicPauseState, DeterministicPauseStatus, PauseControllerOptions, PauseWrappedState } from './pause-resume.js';
|
|
77
79
|
export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsActivePair2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
|
|
78
|
-
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 } from './physics3d.js';
|
|
80
|
+
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';
|
|
81
|
+
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D } from './physics3d-voxel.js';
|
|
82
|
+
export type { DeterministicAuthoritativeBodyProxy3D, DeterministicCosmeticPhysicsBody3D, DeterministicCosmeticPhysicsStepOptions3D, DeterministicCosmeticPhysicsStepResult3D, DeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
79
83
|
export type { DeterministicPhysicsJoint3D } from './physics3d-joints.js';
|
|
80
84
|
export type { DeterministicVehicle3D, DeterministicVehicleImpulse3D, DeterministicVehicleInput3D, DeterministicVehicleStepOptions3D, DeterministicVehicleStepResult3D, DeterministicVehicleWheel3D, DeterministicVehicleWheelTelemetry3D } from './physics3d-vehicle.js';
|
|
81
85
|
export type { DeterministicFrameRuntimeReplayBundleOptions, DeterministicFrameRuntimeReplayBundleVerification, DeterministicReplayBundle, DeterministicReplayBundleMismatch, DeterministicReplayBundleVerification, DeterministicReplayDebugTables, DeterministicReplayDesyncMinimization, DeterministicReplayRuntimeBeforeFrameId, DeterministicReplayRuntimeManifest, DeterministicReplayRuntimeManifestSource, DeterministicReplayRuntimeSystemId, DeterministicReplaySemanticModel, DeterministicReplaySemanticOperation, DeterministicReplaySliceOrigin, DeterministicReplaySnapshot, DeterministicReplayVerificationManifest, ReplayScoreVerificationReason, ReplayScoreVerificationResult } from './replay-bundle.js';
|
|
@@ -90,3 +94,10 @@ export type { DeterministicSignal, DeterministicSignalBus, DeterministicSignalBu
|
|
|
90
94
|
export type { DeterministicSparseSet, DeterministicSparseSetSnapshot } from './sparse-set.js';
|
|
91
95
|
export type { DeterministicLifecycleCounterSummary, DeterministicLifecycleRunner, DeterministicLifecycleSignal, DeterministicLifecycleSnapshot, DeterministicLifecycleSystemContext, DeterministicLifecycleSystemDescriptor, DeterministicLifecycleSystemHooks } from './system-lifecycle.js';
|
|
92
96
|
export type { DeterministicStep, ReplayFile, ReplayVerificationResult } from './types.js';
|
|
97
|
+
export { syncplaySecretConfigHash, syncplaySecretLimits, validateSyncplaySecretSystemsConfig } from './secret-config.js';
|
|
98
|
+
export { decodeSyncplaySecretPrivateMessage, isSyncplaySecretCommand, syncplaySecretProtocol } from './secret-protocol.js';
|
|
99
|
+
export { createBrowserSyncplaySecretCrypto, verifySyncplaySignedReceipt } from './secret-crypto.js';
|
|
100
|
+
export { createSyncplaySecretEventReader, createSyncplaySecretsClient } from './secret-client.js';
|
|
101
|
+
export type { SyncplaySecretSystemDefinition, SyncplaySecretSystemsConfig } from './secret-config.js';
|
|
102
|
+
export type { SyncplayOwnedSecretToken, SyncplaySecretCommand, SyncplaySecretErrorCode, SyncplaySecretPrivateMessage, SyncplaySignedReceipt } from './secret-protocol.js';
|
|
103
|
+
export type { SyncplayChoiceClient, SyncplayChoiceResolvedEvent, SyncplayCollectionClient, SyncplayCollectionRevealedEvent, SyncplayOwnedSecret, SyncplayRandomClient, SyncplayRandomDrawnEvent, SyncplayRoleClient, SyncplayRoleRevealedEvent, SyncplaySecretEventReader, SyncplaySecretsClient } from './secret-client.js';
|
package/dist/browser.js
CHANGED
|
@@ -30,7 +30,9 @@ export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js
|
|
|
30
30
|
export { createOfflineSession } from './offline-session.js';
|
|
31
31
|
export { createPauseController } from './pause-resume.js';
|
|
32
32
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
33
|
-
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
33
|
+
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
34
|
+
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
35
|
+
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
34
36
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
35
37
|
export { createDeterministicR3fRenderAdapter as createDeterministicR3fAdapter, createDeterministicR3fRenderAdapter, interpolateDeterministicRenderNumber, interpolateDeterministicRenderVec2 } from './r3f-render-adapter.js';
|
|
36
38
|
export { verifyReplayWithDescriptor as verifyDeterministicReplay, verifyReplay } from './replay.js';
|
|
@@ -47,3 +49,7 @@ export { decodeDeterministicSessionMessage, deterministicSessionWireVersion, enc
|
|
|
47
49
|
export { createDeterministicSignalBus } from './signals.js';
|
|
48
50
|
export { createDeterministicSparseSet } from './sparse-set.js';
|
|
49
51
|
export { createDeterministicLifecycleRunner, summarizeDeterministicLifecycleCounters } from './system-lifecycle.js';
|
|
52
|
+
export { syncplaySecretConfigHash, syncplaySecretLimits, validateSyncplaySecretSystemsConfig } from './secret-config.js';
|
|
53
|
+
export { decodeSyncplaySecretPrivateMessage, isSyncplaySecretCommand, syncplaySecretProtocol } from './secret-protocol.js';
|
|
54
|
+
export { createBrowserSyncplaySecretCrypto, verifySyncplaySignedReceipt } from './secret-crypto.js';
|
|
55
|
+
export { createSyncplaySecretEventReader, createSyncplaySecretsClient } from './secret-client.js';
|
|
@@ -7,7 +7,9 @@ const input_authority_js_1 = require("./input-authority.js");
|
|
|
7
7
|
const local_authority_wire_js_1 = require("./local-authority-wire.js");
|
|
8
8
|
const session_snapshot_js_1 = require("./session-snapshot.js");
|
|
9
9
|
const session_wire_js_1 = require("./session-wire.js");
|
|
10
|
+
const secret_authority_js_1 = require("./secret-authority.js");
|
|
10
11
|
const DEFAULT_SESSION_MSG_TYPE = 'rdm';
|
|
12
|
+
const DEFAULT_SECRET_MSG_TYPE = 'rdm-secret';
|
|
11
13
|
/** One hour at 60 Hz — see DeterministicAuthorityRoomConfig.historyRetentionTicks. */
|
|
12
14
|
const DEFAULT_HISTORY_RETENTION_TICKS = 216_000;
|
|
13
15
|
/** Frames per history-replay unicast. Bounds message size during catch-up. */
|
|
@@ -19,6 +21,9 @@ const DEFAULT_TRANSFER_TIMEOUT_TICKS = 600;
|
|
|
19
21
|
function createDeterministicAuthorityRoom(transport, config) {
|
|
20
22
|
const sessionMsgType = config.sessionMsgType ?? DEFAULT_SESSION_MSG_TYPE;
|
|
21
23
|
const historyRetentionTicks = config.historyRetentionTicks ?? DEFAULT_HISTORY_RETENTION_TICKS;
|
|
24
|
+
if (config.maxSnapshotBytes !== undefined && (!Number.isInteger(config.maxSnapshotBytes) || config.maxSnapshotBytes < 1)) {
|
|
25
|
+
throw new Error(`createDeterministicAuthorityRoom: maxSnapshotBytes must be a positive integer, received ${config.maxSnapshotBytes}`);
|
|
26
|
+
}
|
|
22
27
|
const authority = (0, input_authority_js_1.createInputAuthority)({
|
|
23
28
|
playerCount: config.playerCount,
|
|
24
29
|
hardToleranceTicks: config.hardToleranceTicks,
|
|
@@ -51,6 +56,18 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
51
56
|
// colliding with a still-present player and orphaning the vacated slot.
|
|
52
57
|
let assignedSlots = 0;
|
|
53
58
|
let now = -1;
|
|
59
|
+
const secretAuthority = config.secrets === undefined
|
|
60
|
+
? undefined
|
|
61
|
+
: (0, secret_authority_js_1.createSyncplaySecretAuthority)({
|
|
62
|
+
authorityId: requireSecretAuthorityId(config.authorityId),
|
|
63
|
+
playerCount: config.playerCount,
|
|
64
|
+
config: config.secrets,
|
|
65
|
+
crypto: requireSecretCrypto(config.secretCrypto),
|
|
66
|
+
sendPrivate(playerId, encoded) {
|
|
67
|
+
transport.sendTo(playerId, config.secretMsgType ?? DEFAULT_SECRET_MSG_TYPE, encoded);
|
|
68
|
+
},
|
|
69
|
+
...(config.restoreFrom?.secretState === undefined ? {} : { restoreFrom: config.restoreFrom.secretState }),
|
|
70
|
+
});
|
|
54
71
|
// M5.5 snapshot acquisition state. The authority stores at most one validated
|
|
55
72
|
// snapshot and runs at most one donor transfer at a time.
|
|
56
73
|
const snapshotCadenceTicks = config.snapshotCadenceTicks ?? DEFAULT_SNAPSHOT_CADENCE_TICKS;
|
|
@@ -230,6 +247,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
230
247
|
const plan = catchUpPlan();
|
|
231
248
|
(0, errors_js_1.invariant)(plan !== 'unavailable', 'a restored room must have a catch-up plan for seated players', 'authority-room.restore-catchup');
|
|
232
249
|
sendSessionStart(playerId, slot);
|
|
250
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
233
251
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
234
252
|
}
|
|
235
253
|
}
|
|
@@ -249,6 +267,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
249
267
|
}
|
|
250
268
|
slotByPlayer.set(playerId, formerSlot);
|
|
251
269
|
sendSessionStart(playerId, formerSlot);
|
|
270
|
+
secretAuthority?.onPlayerReconnected(playerId, formerSlot);
|
|
252
271
|
sendCatchUpTo(playerId, rejoinPlan, 'rejoin');
|
|
253
272
|
return { accepted: true, slot: formerSlot, rejoined: true };
|
|
254
273
|
}
|
|
@@ -267,6 +286,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
267
286
|
slotByPlayer.set(playerId, slot);
|
|
268
287
|
everSlotByPlayer.set(playerId, slot);
|
|
269
288
|
sendSessionStart(playerId, slot);
|
|
289
|
+
secretAuthority?.onPlayerJoined(playerId, slot);
|
|
270
290
|
sendCatchUpTo(playerId, plan, 'join');
|
|
271
291
|
return { accepted: true, slot };
|
|
272
292
|
}
|
|
@@ -286,6 +306,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
286
306
|
return false;
|
|
287
307
|
spectators.add(playerId);
|
|
288
308
|
sendSessionStart(playerId, -1, 'spectator');
|
|
309
|
+
secretAuthority?.onSpectatorJoined(playerId);
|
|
289
310
|
sendCatchUpTo(playerId, plan, 'join');
|
|
290
311
|
return true;
|
|
291
312
|
}
|
|
@@ -312,8 +333,14 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
312
333
|
// client that fell behind the snapshot tick, so the stored snapshot rides
|
|
313
334
|
// along — the client applies it only when it is ahead of its own state.
|
|
314
335
|
const reconnectPlan = !historyReachesTickZero() && storedSnapshot !== undefined ? 'snapshot' : 'history';
|
|
336
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
315
337
|
sendCatchUpTo(playerId, reconnectPlan, 'reconnect');
|
|
316
338
|
}
|
|
339
|
+
function onPlayerSecretMessage(playerId, envelope) {
|
|
340
|
+
if (secretAuthority === undefined)
|
|
341
|
+
return;
|
|
342
|
+
secretAuthority.onPlayerRequest(playerId, slotByPlayer.get(playerId) ?? -1, envelope);
|
|
343
|
+
}
|
|
317
344
|
function onPlayerMessage(playerId, envelope) {
|
|
318
345
|
// Fail closed: unknown/malformed messages are dropped. Snapshot transfer
|
|
319
346
|
// envelopes are the OTHER frozen wire family (schemaVersion vs v field) —
|
|
@@ -519,7 +546,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
519
546
|
donorId,
|
|
520
547
|
minFrame: tick + 1,
|
|
521
548
|
startedAt: now,
|
|
522
|
-
collector: (0, session_snapshot_js_1.createSnapshotTransferCollector)(),
|
|
549
|
+
collector: (0, session_snapshot_js_1.createSnapshotTransferCollector)(config.maxSnapshotBytes === undefined ? {} : { maxSnapshotBytes: config.maxSnapshotBytes }),
|
|
523
550
|
isRetry,
|
|
524
551
|
};
|
|
525
552
|
transport.sendTo(donorId, sessionMsgType, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotRequestEnvelope)(nextWireSequence(), { descriptorHash: roomDescriptorHash }, tick + 1));
|
|
@@ -574,7 +601,10 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
574
601
|
// Fixed roster for v1 (late-join/reconnect is M5). Dropping the mapping stops accepting the
|
|
575
602
|
// departed player's input; their slot stays reserved because `assignedSlots` never decreases,
|
|
576
603
|
// so the authority substitutes for it rather than handing the number to a new joiner.
|
|
604
|
+
const slot = slotByPlayer.get(playerId);
|
|
577
605
|
slotByPlayer.delete(playerId);
|
|
606
|
+
if (slot !== undefined)
|
|
607
|
+
secretAuthority?.onPlayerLeft(playerId, slot);
|
|
578
608
|
if (pendingTransfer?.donorId === playerId) {
|
|
579
609
|
failPendingTransfer('snapshot-donor-left');
|
|
580
610
|
}
|
|
@@ -589,6 +619,10 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
589
619
|
return;
|
|
590
620
|
}
|
|
591
621
|
now += 1;
|
|
622
|
+
authority.recordOccupiedSlots(now, [...slotByPlayer.values()]);
|
|
623
|
+
for (const command of secretAuthority?.onTick(now, [...slotByPlayer.values()]) ?? []) {
|
|
624
|
+
authority.queueCommand(now, command);
|
|
625
|
+
}
|
|
592
626
|
authority.advance(now);
|
|
593
627
|
maintainSnapshotAcquisition();
|
|
594
628
|
if (authority.confirmedThrough < 0) {
|
|
@@ -639,6 +673,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
639
673
|
onPlayerJoin,
|
|
640
674
|
onSpectatorJoin,
|
|
641
675
|
onPlayerMessage,
|
|
676
|
+
onPlayerSecretMessage,
|
|
642
677
|
onPlayerLeave,
|
|
643
678
|
onPlayerReconnected,
|
|
644
679
|
onTick,
|
|
@@ -675,6 +710,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
675
710
|
if (plan === 'unavailable')
|
|
676
711
|
return false;
|
|
677
712
|
sendSessionStart(playerId, slot);
|
|
713
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
678
714
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
679
715
|
return true;
|
|
680
716
|
},
|
|
@@ -700,5 +736,16 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
700
736
|
frames: authority.confirmedFrameRange(0, authority.confirmedThrough),
|
|
701
737
|
};
|
|
702
738
|
},
|
|
739
|
+
secretSnapshot() {
|
|
740
|
+
return secretAuthority?.snapshot();
|
|
741
|
+
},
|
|
703
742
|
};
|
|
704
743
|
}
|
|
744
|
+
function requireSecretAuthorityId(authorityId) {
|
|
745
|
+
(0, errors_js_1.invariant)(typeof authorityId === 'string' && authorityId.length > 0, 'secret authorityId is required', 'authority-room.secret-authority-id');
|
|
746
|
+
return authorityId;
|
|
747
|
+
}
|
|
748
|
+
function requireSecretCrypto(crypto) {
|
|
749
|
+
(0, errors_js_1.invariant)(crypto !== undefined, 'secret signing is unavailable', 'authority-room.secret-signing');
|
|
750
|
+
return crypto;
|
|
751
|
+
}
|
package/dist/cjs/browser.js
CHANGED
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.sortAabbsByStableId = exports.segmentsIntersect = exports.segmentSegmentDistance = exports.segmentPointDistance = exports.pointOnSegment = exports.orientation = exports.fixedVec2 = exports.fixedCircle = exports.fixedAabb = exports.closestPointOnSegment = exports.circleIntersectsAabb = exports.circleIntersects = exports.aabbIntersects = exports.aabbContainsPoint = exports.stableSortedKeys = exports.stableEntries = exports.deterministicShuffle = exports.createStableIndexedMap = exports.encodeUtf8 = exports.encodeCanonicalBytes = exports.defaultChecksum = exports.decodeUtf8 = exports.decodeCanonicalValue = exports.decodeCanonicalBytes = exports.cloneCanonical = exports.canonicalStringify = exports.canonicalize = exports.tickDeterministicHfsm = exports.tickDeterministicBehaviorTreeWithMemory = exports.tickDeterministicBehaviorTree = exports.selectDeterministicUtility = exports.runDeterministicBotSimulation = exports.getBlackboardValue = exports.createDeterministicBotBlackboard = exports.cookDeterministicBotAsset = exports.traceDeterministicBotDocument = exports.summarizeDeterministicBotDocumentTraceCoverage = exports.summarizeDeterministicBotDocumentTraceCounterDelta = exports.compileDeterministicBotDocument = exports.createDeterministicAuthorityRoom = exports.validatePrototypeEntitiesAgainstFrameRuntime = exports.stableAssetCookJson = exports.materializePrototypeEntities = exports.materializeDeterministicMapEntities = exports.cookDeterministicAssets = exports.assertAssetManifestMatches = exports.stepDeterministicAnimation = exports.reconstructDeterministicAnimationEvents = exports.createDeterministicAnimationRuntimeState = exports.cookDeterministicAnimation = void 0;
|
|
5
5
|
exports.createDeterministicLocalAuthoritySnapshotOfferEnvelope = exports.createDeterministicLocalAuthoritySnapshotEnvelope = exports.createDeterministicLocalAuthoritySnapshotCompleteEnvelope = exports.createDeterministicLocalAuthoritySnapshotChunkEnvelope = exports.createDeterministicLocalAuthorityInputBundleEnvelope = exports.createDeterministicLocalAuthorityCorrectionEnvelope = exports.createDeterministicLocalAuthorityCommandBundleEnvelope = exports.createDeterministicLocalAuthorityAckEnvelope = exports.applyDeterministicLocalAuthorityInputBundleEnvelope = exports.applyDeterministicLocalAuthorityCommandBundleEnvelope = exports.createDeterministicLocalAuthorityRuntime = exports.validateDeterministicLagCompensatedShot3D = exports.validateDeterministicAuthoritativeLagCompensatedShot3D = exports.rewindDeterministicLagCompensatedTargets3D = exports.createDeterministicLagCompensation3DHistory = exports.buildDeterministicLagCompensatedHitboxProxies3D = exports.runInstantReplay = exports.createInstantReplayRunner = exports.sortDeterministicInputTimelineFlags = exports.evaluateDeterministicInputTimelineResult = exports.accumulateDeterministicInputTimelineState = exports.createInputAuthority = exports.filterDeterministicEventsForPlayer = exports.createDeterministicEventTimeline = exports.validateDeterministicFrameSystemsStateless = exports.runDeterministicFrameRuntimeMutationCorpus = exports.isDeterministicFrameHeapStaleHandleError = exports.generateDeterministicFrameRuntimeSource = exports.createDeterministicSparseSetFrameRuntime = exports.createDeterministicSlotMap = exports.createDeterministicFrameRuntimeAccessors = exports.createDeterministicFrameRuntime = exports.createDeterministicBitset = exports.compileDeterministicDslRuntimeDescriptor = exports.assertDeterministicFrameSystemsStateless = exports.assertDeterministicFrameHeapStaleHandleError = exports.createDeterministicEventEffectsAdapter = exports.verifyDeterministicSessionRuntimeReplay = exports.createDeterministicSessionReplayBundle = exports.createDeterministicSessionCore = exports.verifyDeterministicConfigBundle = exports.summarizeDeterministicConfigBundleFailureReasons = exports.evaluateDeterministicConfigBundleParityResult = exports.cookDeterministicConfigBundle = exports.evaluateDeterministicCommandTimelineMigrationResult = exports.deterministicPayloadMatchesFields = exports.createDeterministicCommandTimelineSnapshot = exports.createDeterministicCommandTimeline = exports.assertDeterministicPayloadFields = exports.translateAabb = void 0;
|
|
6
6
|
exports.stepDeterministicPhysicsWorld2D = exports.raycastDeterministicPhysics2D = exports.raycastAllDeterministicPhysics2D = exports.queryDeterministicPhysicsShapeOverlap2D = exports.queryDeterministicPhysicsAabb2D = exports.nearestDeterministicPhysicsHit2D = exports.createDeterministicPhysicsWorld2D = exports.circleCastDeterministicPhysics2D = exports.createPauseController = exports.createOfflineSession = exports.valueNoise2D = exports.valueNoise1D = exports.hashUint32 = exports.hash2 = exports.fbm2D = exports.createNetworkedSyncplayClient = exports.stepDeterministicNavmeshOffMeshTraversal = exports.stepDeterministicNavmeshAgents = exports.smoothDeterministicNavmeshPath = exports.runDeterministicNavmeshStress = exports.locateDeterministicNavmeshPolygon = exports.findDeterministicNavmeshPointPath = exports.findDeterministicNavmeshPath = exports.cookImportedDeterministicNavmesh = exports.cookDeterministicNavmesh = exports.stepDeterministicNavigationAgents = exports.smoothDeterministicPath = exports.findDeterministicPath = exports.createDeterministicFlowField = exports.cookTilemapNavigationGraph = exports.cookDeterministicNavigationGraph = exports.applyDeterministicReciprocalAvoidance = exports.createSyncplayClient = exports.stepDeterministicKcc3D = exports.stepDeterministicCarry3D = exports.validateDeterministicLagCompensatedHit = exports.stepDeterministicTopDownMover = exports.stepDeterministicKcc2D = exports.stepDeterministicFpsAim = exports.runDeterministicMovementSimulation = exports.runDeterministicMovementCrowdSimulation = exports.rewindDeterministicTargets = exports.createDeterministicMath = exports.encodeDeterministicLocalAuthorityWireEnvelope = exports.deterministicLocalAuthorityWireProtocol = exports.decodeDeterministicLocalAuthorityWireEnvelope = exports.createDeterministicLocalAuthorityWireReceiver = exports.createDeterministicLocalAuthorityWireAuth = exports.createDeterministicLocalAuthoritySnapshotTransfer = exports.createDeterministicLocalAuthoritySnapshotRequestEnvelope = void 0;
|
|
7
|
-
exports.
|
|
8
|
-
exports.summarizeDeterministicLifecycleCounters = exports.createDeterministicLifecycleRunner = exports.createDeterministicSparseSet = exports.createDeterministicSignalBus = exports.encodeDeterministicSessionMessage = exports.deterministicSessionWireVersion = exports.decodeDeterministicSessionMessage = exports.encodeDeterministicStateSnapshot = exports.deterministicRoomDescriptorHash = exports.DEFAULT_SNAPSHOT_CHUNK_SIZE = exports.DEFAULT_MAX_SNAPSHOT_BYTES = exports.decodeDeterministicStateSnapshot = exports.createSnapshotTransferFromBytes = exports.createSnapshotTransferCollector = void 0;
|
|
7
|
+
exports.createSyncplayOfflineRuntime = exports.createSyncplayGameRuntime = exports.stableDeterministicDslSchemaJson = exports.parseDeterministicSchemaDsl = exports.deterministicDslMaxPlayers = exports.createDeterministicBinarySerializer = exports.proveSchemaBinaryRoundTrip = exports.generateSchemaTypes = exports.generateSchemaBinarySource = exports.createDeterministicDescriptorBinarySchema = exports.createSchemaArtifacts = exports.stableDescriptorId = exports.defineSyncplayGame = exports.verifyReplayScoreAgainstTrustedRuntime = exports.verifyDeterministicSelfContainedFrameRuntimeReplayBundle = exports.verifyDeterministicReplayBundle = exports.verifyDeterministicFrameRuntimeReplayBundle = exports.sliceDeterministicReplayBundle = exports.minimizeDeterministicReplayDesyncBundle = exports.minimizeDeterministicReplayDesync = exports.createDeterministicReplayRuntimeManifest = exports.createDeterministicReplayDebugTables = exports.createDeterministicReplayBundle = exports.verifyReplay = exports.verifyDeterministicReplay = exports.interpolateDeterministicRenderVec2 = exports.interpolateDeterministicRenderNumber = exports.createDeterministicR3fRenderAdapter = exports.createDeterministicR3fAdapter = exports.stepDeterministicVehicle3D = exports.stepDeterministicCosmeticPhysicsWorld3D = exports.setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D = exports.createDeterministicCosmeticPhysicsWorld3D = exports.cookDeterministicVoxelChunkCollider3D = exports.stepDeterministicPhysicsWorld3D = exports.shapeCastDeterministicPhysics3DDetailed = exports.shapeCastDeterministicPhysics3D = exports.setDeterministicBodyVelocity3D = exports.runDeterministicPhysics3DStress = exports.raycastDeterministicPhysics3DDetailed = exports.raycastDeterministicPhysics3D = exports.overlapDeterministicPhysics3D = exports.deterministicPhysicsCallback3D = exports.deterministicPhysicsBodyById3D = exports.deriveInverseInertia = exports.createDeterministicPhysicsWorld3D = exports.contactDetailsDeterministicPhysics3D = exports.applyDeterministicWorldEdit3D = exports.applyDeterministicImpulses3D = exports.applyDeterministicImpulse3D = void 0;
|
|
8
|
+
exports.createSyncplaySecretsClient = exports.createSyncplaySecretEventReader = exports.verifySyncplaySignedReceipt = exports.createBrowserSyncplaySecretCrypto = exports.syncplaySecretProtocol = exports.isSyncplaySecretCommand = exports.decodeSyncplaySecretPrivateMessage = exports.validateSyncplaySecretSystemsConfig = exports.syncplaySecretLimits = exports.syncplaySecretConfigHash = exports.summarizeDeterministicLifecycleCounters = exports.createDeterministicLifecycleRunner = exports.createDeterministicSparseSet = exports.createDeterministicSignalBus = exports.encodeDeterministicSessionMessage = exports.deterministicSessionWireVersion = exports.decodeDeterministicSessionMessage = exports.encodeDeterministicStateSnapshot = exports.deterministicRoomDescriptorHash = exports.DEFAULT_SNAPSHOT_CHUNK_SIZE = exports.DEFAULT_MAX_SNAPSHOT_BYTES = exports.decodeDeterministicStateSnapshot = exports.createSnapshotTransferFromBytes = exports.createSnapshotTransferCollector = exports.bytesToBase64 = exports.base64ToBytes = exports.exportPromotionSnapshot = exports.createSessionFromPromotionSnapshot = exports.createSyncplaySession = void 0;
|
|
9
9
|
var animation_js_1 = require("./animation.js");
|
|
10
10
|
Object.defineProperty(exports, "cookDeterministicAnimation", { enumerable: true, get: function () { return animation_js_1.cookDeterministicAnimation; } });
|
|
11
11
|
Object.defineProperty(exports, "createDeterministicAnimationRuntimeState", { enumerable: true, get: function () { return animation_js_1.createDeterministicAnimationRuntimeState; } });
|
|
@@ -190,6 +190,7 @@ Object.defineProperty(exports, "stepDeterministicPhysicsWorld2D", { enumerable:
|
|
|
190
190
|
var physics3d_js_1 = require("./physics3d.js");
|
|
191
191
|
Object.defineProperty(exports, "applyDeterministicImpulse3D", { enumerable: true, get: function () { return physics3d_js_1.applyDeterministicImpulse3D; } });
|
|
192
192
|
Object.defineProperty(exports, "applyDeterministicImpulses3D", { enumerable: true, get: function () { return physics3d_js_1.applyDeterministicImpulses3D; } });
|
|
193
|
+
Object.defineProperty(exports, "applyDeterministicWorldEdit3D", { enumerable: true, get: function () { return physics3d_js_1.applyDeterministicWorldEdit3D; } });
|
|
193
194
|
Object.defineProperty(exports, "contactDetailsDeterministicPhysics3D", { enumerable: true, get: function () { return physics3d_js_1.contactDetailsDeterministicPhysics3D; } });
|
|
194
195
|
Object.defineProperty(exports, "createDeterministicPhysicsWorld3D", { enumerable: true, get: function () { return physics3d_js_1.createDeterministicPhysicsWorld3D; } });
|
|
195
196
|
Object.defineProperty(exports, "deriveInverseInertia", { enumerable: true, get: function () { return physics3d_js_1.deriveInverseInertia; } });
|
|
@@ -203,6 +204,12 @@ Object.defineProperty(exports, "setDeterministicBodyVelocity3D", { enumerable: t
|
|
|
203
204
|
Object.defineProperty(exports, "shapeCastDeterministicPhysics3D", { enumerable: true, get: function () { return physics3d_js_1.shapeCastDeterministicPhysics3D; } });
|
|
204
205
|
Object.defineProperty(exports, "shapeCastDeterministicPhysics3DDetailed", { enumerable: true, get: function () { return physics3d_js_1.shapeCastDeterministicPhysics3DDetailed; } });
|
|
205
206
|
Object.defineProperty(exports, "stepDeterministicPhysicsWorld3D", { enumerable: true, get: function () { return physics3d_js_1.stepDeterministicPhysicsWorld3D; } });
|
|
207
|
+
var physics3d_voxel_js_1 = require("./physics3d-voxel.js");
|
|
208
|
+
Object.defineProperty(exports, "cookDeterministicVoxelChunkCollider3D", { enumerable: true, get: function () { return physics3d_voxel_js_1.cookDeterministicVoxelChunkCollider3D; } });
|
|
209
|
+
var cosmetic_physics3d_js_1 = require("./cosmetic-physics3d.js");
|
|
210
|
+
Object.defineProperty(exports, "createDeterministicCosmeticPhysicsWorld3D", { enumerable: true, get: function () { return cosmetic_physics3d_js_1.createDeterministicCosmeticPhysicsWorld3D; } });
|
|
211
|
+
Object.defineProperty(exports, "setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D", { enumerable: true, get: function () { return cosmetic_physics3d_js_1.setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D; } });
|
|
212
|
+
Object.defineProperty(exports, "stepDeterministicCosmeticPhysicsWorld3D", { enumerable: true, get: function () { return cosmetic_physics3d_js_1.stepDeterministicCosmeticPhysicsWorld3D; } });
|
|
206
213
|
var physics3d_vehicle_js_1 = require("./physics3d-vehicle.js");
|
|
207
214
|
Object.defineProperty(exports, "stepDeterministicVehicle3D", { enumerable: true, get: function () { return physics3d_vehicle_js_1.stepDeterministicVehicle3D; } });
|
|
208
215
|
var r3f_render_adapter_js_1 = require("./r3f-render-adapter.js");
|
|
@@ -268,3 +275,17 @@ Object.defineProperty(exports, "createDeterministicSparseSet", { enumerable: tru
|
|
|
268
275
|
var system_lifecycle_js_1 = require("./system-lifecycle.js");
|
|
269
276
|
Object.defineProperty(exports, "createDeterministicLifecycleRunner", { enumerable: true, get: function () { return system_lifecycle_js_1.createDeterministicLifecycleRunner; } });
|
|
270
277
|
Object.defineProperty(exports, "summarizeDeterministicLifecycleCounters", { enumerable: true, get: function () { return system_lifecycle_js_1.summarizeDeterministicLifecycleCounters; } });
|
|
278
|
+
var secret_config_js_1 = require("./secret-config.js");
|
|
279
|
+
Object.defineProperty(exports, "syncplaySecretConfigHash", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretConfigHash; } });
|
|
280
|
+
Object.defineProperty(exports, "syncplaySecretLimits", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretLimits; } });
|
|
281
|
+
Object.defineProperty(exports, "validateSyncplaySecretSystemsConfig", { enumerable: true, get: function () { return secret_config_js_1.validateSyncplaySecretSystemsConfig; } });
|
|
282
|
+
var secret_protocol_js_1 = require("./secret-protocol.js");
|
|
283
|
+
Object.defineProperty(exports, "decodeSyncplaySecretPrivateMessage", { enumerable: true, get: function () { return secret_protocol_js_1.decodeSyncplaySecretPrivateMessage; } });
|
|
284
|
+
Object.defineProperty(exports, "isSyncplaySecretCommand", { enumerable: true, get: function () { return secret_protocol_js_1.isSyncplaySecretCommand; } });
|
|
285
|
+
Object.defineProperty(exports, "syncplaySecretProtocol", { enumerable: true, get: function () { return secret_protocol_js_1.syncplaySecretProtocol; } });
|
|
286
|
+
var secret_crypto_js_1 = require("./secret-crypto.js");
|
|
287
|
+
Object.defineProperty(exports, "createBrowserSyncplaySecretCrypto", { enumerable: true, get: function () { return secret_crypto_js_1.createBrowserSyncplaySecretCrypto; } });
|
|
288
|
+
Object.defineProperty(exports, "verifySyncplaySignedReceipt", { enumerable: true, get: function () { return secret_crypto_js_1.verifySyncplaySignedReceipt; } });
|
|
289
|
+
var secret_client_js_1 = require("./secret-client.js");
|
|
290
|
+
Object.defineProperty(exports, "createSyncplaySecretEventReader", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretEventReader; } });
|
|
291
|
+
Object.defineProperty(exports, "createSyncplaySecretsClient", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretsClient; } });
|