@series-inc/rundot-syncplay 5.24.0-beta.0 → 5.24.0-beta.1
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 +33 -12
- package/dist/authority-room.d.ts +11 -0
- package/dist/authority-room.js +43 -0
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +4 -0
- package/dist/cjs/authority-room.js +43 -0
- package/dist/cjs/browser.js +15 -1
- package/dist/cjs/engine-complete.js +1 -4
- package/dist/cjs/engine-parity-matrix.js +11 -2
- package/dist/cjs/engine-parity.js +1 -1
- package/dist/cjs/index.js +17 -1
- package/dist/cjs/input-authority.js +34 -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/networked-client.js +59 -11
- package/dist/cjs/node.js +19 -1
- package/dist/cjs/physics-cert.js +1 -1
- 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 +109 -19
- package/dist/cjs/physics3d-toi.js +23 -0
- package/dist/cjs/physics3d.js +206 -24
- package/dist/cjs/replay.js +25 -1
- package/dist/cjs/sdk-offline.js +9 -1
- package/dist/cjs/sdk-session.js +8 -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/tools/static-checker.js +61 -13
- package/dist/cli.js +30 -7
- package/dist/deterministic-primitives.d.ts +1 -0
- package/dist/engine-complete.js +1 -4
- package/dist/engine-parity-matrix.js +11 -2
- package/dist/engine-parity.js +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +5 -0
- package/dist/input-authority.d.ts +3 -0
- package/dist/input-authority.js +35 -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/networked-client.d.ts +8 -0
- package/dist/networked-client.js +59 -11
- package/dist/node.d.ts +5 -0
- package/dist/node.js +4 -0
- package/dist/physics-cert.js +1 -1
- 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 +15 -3
- package/dist/physics3d-solver.js +108 -19
- package/dist/physics3d-toi.d.ts +9 -0
- package/dist/physics3d-toi.js +22 -0
- package/dist/physics3d.d.ts +8 -1
- package/dist/physics3d.js +209 -27
- 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 +4 -0
- package/dist/sdk-session.js +8 -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/tools/static-checker.d.ts +11 -0
- package/dist/tools/static-checker.js +60 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -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
|
-
##
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
## Public and private projections
|
|
12
|
+
|
|
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.
|
|
19
|
+
|
|
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
|
|
|
@@ -557,6 +568,16 @@ remediation text. Valid suppressions must use
|
|
|
557
568
|
`// rdm-ignore-next-line <rule-id>: <reason>` and are reported with file, line,
|
|
558
569
|
rule, and reason; invalid suppressions are errors.
|
|
559
570
|
|
|
571
|
+
`static:determinism` with explicit paths (`--entry`, `--paths`) follows the
|
|
572
|
+
import graph transitively by default (`--no-follow-imports` opts out); the
|
|
573
|
+
JSON artifact records `requestedPaths` (CLI inputs), `checkedPaths` (files
|
|
574
|
+
actually checked, post-traversal), `followImports`, and `unresolvedImports`
|
|
575
|
+
(relative imports that resolve to no file — unchecked holes). Bare package
|
|
576
|
+
specifiers are never followed; if your simulation uses tsconfig path aliases,
|
|
577
|
+
pass `--project tsconfig.json` so aliased imports resolve and get checked —
|
|
578
|
+
without it they are skipped like package imports. Package self-certification
|
|
579
|
+
(no explicit paths) checks its curated source list without following imports.
|
|
580
|
+
|
|
560
581
|
The checker covers host random/time, timers, async simulation work, I/O,
|
|
561
582
|
unordered object iteration, random sort comparators, render-object mutation,
|
|
562
583
|
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
|
*
|
|
@@ -89,6 +92,10 @@ export interface DeterministicAuthorityRoomConfig {
|
|
|
89
92
|
* MUST call `resyncSeatedPlayers()` so surviving clients re-seat.
|
|
90
93
|
*/
|
|
91
94
|
readonly restoreFrom?: DeterministicAuthorityRoomRestoreState;
|
|
95
|
+
readonly authorityId?: string;
|
|
96
|
+
readonly secrets?: SyncplaySecretSystemsConfig;
|
|
97
|
+
readonly secretCrypto?: SyncplaySecretAuthorityCrypto;
|
|
98
|
+
readonly secretMsgType?: string;
|
|
92
99
|
}
|
|
93
100
|
export interface DeterministicAuthorityRoomRestoreState {
|
|
94
101
|
readonly now: number;
|
|
@@ -98,6 +105,7 @@ export interface DeterministicAuthorityRoomRestoreState {
|
|
|
98
105
|
readonly tail: readonly ConfirmedInputFrame[];
|
|
99
106
|
readonly snapshotBytes?: string;
|
|
100
107
|
readonly schemaHash?: string;
|
|
108
|
+
readonly secretState?: SyncplaySecretAuthoritySnapshot;
|
|
101
109
|
}
|
|
102
110
|
export interface AuthorityRoomCatchUpEvent {
|
|
103
111
|
readonly playerId: string;
|
|
@@ -178,6 +186,7 @@ export interface DeterministicAuthorityRoom {
|
|
|
178
186
|
/** Seat a read-only spectator (G11): role 'spectator', no slot, full catch-up + live follow. */
|
|
179
187
|
onSpectatorJoin(playerId: string): boolean;
|
|
180
188
|
onPlayerMessage(playerId: string, envelope: string): void;
|
|
189
|
+
onPlayerSecretMessage(playerId: string, envelope: string): void;
|
|
181
190
|
onPlayerLeave(playerId: string): void;
|
|
182
191
|
/**
|
|
183
192
|
* A player's transport re-attached inside the host's grace window (socket
|
|
@@ -213,5 +222,7 @@ export interface DeterministicAuthorityRoom {
|
|
|
213
222
|
* `preserveFullHistory`, or the match outlived `historyRetentionTicks`).
|
|
214
223
|
*/
|
|
215
224
|
matchLog(): SyncplayMatchLog | undefined;
|
|
225
|
+
/** Server-only secret state. Never include this value in public snapshots or match logs. */
|
|
226
|
+
secretSnapshot(): SyncplaySecretAuthoritySnapshot | undefined;
|
|
216
227
|
}
|
|
217
228
|
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. */
|
|
@@ -48,6 +50,18 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
48
50
|
// colliding with a still-present player and orphaning the vacated slot.
|
|
49
51
|
let assignedSlots = 0;
|
|
50
52
|
let now = -1;
|
|
53
|
+
const secretAuthority = config.secrets === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: createSyncplaySecretAuthority({
|
|
56
|
+
authorityId: requireSecretAuthorityId(config.authorityId),
|
|
57
|
+
playerCount: config.playerCount,
|
|
58
|
+
config: config.secrets,
|
|
59
|
+
crypto: requireSecretCrypto(config.secretCrypto),
|
|
60
|
+
sendPrivate(playerId, encoded) {
|
|
61
|
+
transport.sendTo(playerId, config.secretMsgType ?? DEFAULT_SECRET_MSG_TYPE, encoded);
|
|
62
|
+
},
|
|
63
|
+
...(config.restoreFrom?.secretState === undefined ? {} : { restoreFrom: config.restoreFrom.secretState }),
|
|
64
|
+
});
|
|
51
65
|
// M5.5 snapshot acquisition state. The authority stores at most one validated
|
|
52
66
|
// snapshot and runs at most one donor transfer at a time.
|
|
53
67
|
const snapshotCadenceTicks = config.snapshotCadenceTicks ?? DEFAULT_SNAPSHOT_CADENCE_TICKS;
|
|
@@ -227,6 +241,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
227
241
|
const plan = catchUpPlan();
|
|
228
242
|
invariant(plan !== 'unavailable', 'a restored room must have a catch-up plan for seated players', 'authority-room.restore-catchup');
|
|
229
243
|
sendSessionStart(playerId, slot);
|
|
244
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
230
245
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
231
246
|
}
|
|
232
247
|
}
|
|
@@ -246,6 +261,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
246
261
|
}
|
|
247
262
|
slotByPlayer.set(playerId, formerSlot);
|
|
248
263
|
sendSessionStart(playerId, formerSlot);
|
|
264
|
+
secretAuthority?.onPlayerReconnected(playerId, formerSlot);
|
|
249
265
|
sendCatchUpTo(playerId, rejoinPlan, 'rejoin');
|
|
250
266
|
return { accepted: true, slot: formerSlot, rejoined: true };
|
|
251
267
|
}
|
|
@@ -264,6 +280,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
264
280
|
slotByPlayer.set(playerId, slot);
|
|
265
281
|
everSlotByPlayer.set(playerId, slot);
|
|
266
282
|
sendSessionStart(playerId, slot);
|
|
283
|
+
secretAuthority?.onPlayerJoined(playerId, slot);
|
|
267
284
|
sendCatchUpTo(playerId, plan, 'join');
|
|
268
285
|
return { accepted: true, slot };
|
|
269
286
|
}
|
|
@@ -283,6 +300,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
283
300
|
return false;
|
|
284
301
|
spectators.add(playerId);
|
|
285
302
|
sendSessionStart(playerId, -1, 'spectator');
|
|
303
|
+
secretAuthority?.onSpectatorJoined(playerId);
|
|
286
304
|
sendCatchUpTo(playerId, plan, 'join');
|
|
287
305
|
return true;
|
|
288
306
|
}
|
|
@@ -309,8 +327,14 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
309
327
|
// client that fell behind the snapshot tick, so the stored snapshot rides
|
|
310
328
|
// along — the client applies it only when it is ahead of its own state.
|
|
311
329
|
const reconnectPlan = !historyReachesTickZero() && storedSnapshot !== undefined ? 'snapshot' : 'history';
|
|
330
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
312
331
|
sendCatchUpTo(playerId, reconnectPlan, 'reconnect');
|
|
313
332
|
}
|
|
333
|
+
function onPlayerSecretMessage(playerId, envelope) {
|
|
334
|
+
if (secretAuthority === undefined)
|
|
335
|
+
return;
|
|
336
|
+
secretAuthority.onPlayerRequest(playerId, slotByPlayer.get(playerId) ?? -1, envelope);
|
|
337
|
+
}
|
|
314
338
|
function onPlayerMessage(playerId, envelope) {
|
|
315
339
|
// Fail closed: unknown/malformed messages are dropped. Snapshot transfer
|
|
316
340
|
// envelopes are the OTHER frozen wire family (schemaVersion vs v field) —
|
|
@@ -571,7 +595,10 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
571
595
|
// Fixed roster for v1 (late-join/reconnect is M5). Dropping the mapping stops accepting the
|
|
572
596
|
// departed player's input; their slot stays reserved because `assignedSlots` never decreases,
|
|
573
597
|
// so the authority substitutes for it rather than handing the number to a new joiner.
|
|
598
|
+
const slot = slotByPlayer.get(playerId);
|
|
574
599
|
slotByPlayer.delete(playerId);
|
|
600
|
+
if (slot !== undefined)
|
|
601
|
+
secretAuthority?.onPlayerLeft(playerId, slot);
|
|
575
602
|
if (pendingTransfer?.donorId === playerId) {
|
|
576
603
|
failPendingTransfer('snapshot-donor-left');
|
|
577
604
|
}
|
|
@@ -586,6 +613,9 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
586
613
|
return;
|
|
587
614
|
}
|
|
588
615
|
now += 1;
|
|
616
|
+
for (const command of secretAuthority?.onTick(now, [...slotByPlayer.values()]) ?? []) {
|
|
617
|
+
authority.queueCommand(now, command);
|
|
618
|
+
}
|
|
589
619
|
authority.advance(now);
|
|
590
620
|
maintainSnapshotAcquisition();
|
|
591
621
|
if (authority.confirmedThrough < 0) {
|
|
@@ -636,6 +666,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
636
666
|
onPlayerJoin,
|
|
637
667
|
onSpectatorJoin,
|
|
638
668
|
onPlayerMessage,
|
|
669
|
+
onPlayerSecretMessage,
|
|
639
670
|
onPlayerLeave,
|
|
640
671
|
onPlayerReconnected,
|
|
641
672
|
onTick,
|
|
@@ -672,6 +703,7 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
672
703
|
if (plan === 'unavailable')
|
|
673
704
|
return false;
|
|
674
705
|
sendSessionStart(playerId, slot);
|
|
706
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
675
707
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
676
708
|
return true;
|
|
677
709
|
},
|
|
@@ -697,5 +729,16 @@ export function createDeterministicAuthorityRoom(transport, config) {
|
|
|
697
729
|
frames: authority.confirmedFrameRange(0, authority.confirmedThrough),
|
|
698
730
|
};
|
|
699
731
|
},
|
|
732
|
+
secretSnapshot() {
|
|
733
|
+
return secretAuthority?.snapshot();
|
|
734
|
+
},
|
|
700
735
|
};
|
|
701
736
|
}
|
|
737
|
+
function requireSecretAuthorityId(authorityId) {
|
|
738
|
+
invariant(typeof authorityId === 'string' && authorityId.length > 0, 'secret authorityId is required', 'authority-room.secret-authority-id');
|
|
739
|
+
return authorityId;
|
|
740
|
+
}
|
|
741
|
+
function requireSecretCrypto(crypto) {
|
|
742
|
+
invariant(crypto !== undefined, 'secret signing is unavailable', 'authority-room.secret-signing');
|
|
743
|
+
return crypto;
|
|
744
|
+
}
|
package/dist/browser.d.ts
CHANGED
|
@@ -90,3 +90,10 @@ export type { DeterministicSignal, DeterministicSignalBus, DeterministicSignalBu
|
|
|
90
90
|
export type { DeterministicSparseSet, DeterministicSparseSetSnapshot } from './sparse-set.js';
|
|
91
91
|
export type { DeterministicLifecycleCounterSummary, DeterministicLifecycleRunner, DeterministicLifecycleSignal, DeterministicLifecycleSnapshot, DeterministicLifecycleSystemContext, DeterministicLifecycleSystemDescriptor, DeterministicLifecycleSystemHooks } from './system-lifecycle.js';
|
|
92
92
|
export type { DeterministicStep, ReplayFile, ReplayVerificationResult } from './types.js';
|
|
93
|
+
export { syncplaySecretConfigHash, syncplaySecretLimits, validateSyncplaySecretSystemsConfig } from './secret-config.js';
|
|
94
|
+
export { decodeSyncplaySecretPrivateMessage, isSyncplaySecretCommand, syncplaySecretProtocol } from './secret-protocol.js';
|
|
95
|
+
export { createBrowserSyncplaySecretCrypto, verifySyncplaySignedReceipt } from './secret-crypto.js';
|
|
96
|
+
export { createSyncplaySecretEventReader, createSyncplaySecretsClient } from './secret-client.js';
|
|
97
|
+
export type { SyncplaySecretSystemDefinition, SyncplaySecretSystemsConfig } from './secret-config.js';
|
|
98
|
+
export type { SyncplayOwnedSecretToken, SyncplaySecretCommand, SyncplaySecretErrorCode, SyncplaySecretPrivateMessage, SyncplaySignedReceipt } from './secret-protocol.js';
|
|
99
|
+
export type { SyncplayChoiceClient, SyncplayChoiceResolvedEvent, SyncplayCollectionClient, SyncplayCollectionRevealedEvent, SyncplayOwnedSecret, SyncplayRandomClient, SyncplayRandomDrawnEvent, SyncplayRoleClient, SyncplayRoleRevealedEvent, SyncplaySecretEventReader, SyncplaySecretsClient } from './secret-client.js';
|
package/dist/browser.js
CHANGED
|
@@ -47,3 +47,7 @@ export { decodeDeterministicSessionMessage, deterministicSessionWireVersion, enc
|
|
|
47
47
|
export { createDeterministicSignalBus } from './signals.js';
|
|
48
48
|
export { createDeterministicSparseSet } from './sparse-set.js';
|
|
49
49
|
export { createDeterministicLifecycleRunner, summarizeDeterministicLifecycleCounters } from './system-lifecycle.js';
|
|
50
|
+
export { syncplaySecretConfigHash, syncplaySecretLimits, validateSyncplaySecretSystemsConfig } from './secret-config.js';
|
|
51
|
+
export { decodeSyncplaySecretPrivateMessage, isSyncplaySecretCommand, syncplaySecretProtocol } from './secret-protocol.js';
|
|
52
|
+
export { createBrowserSyncplaySecretCrypto, verifySyncplaySignedReceipt } from './secret-crypto.js';
|
|
53
|
+
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. */
|
|
@@ -51,6 +53,18 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
51
53
|
// colliding with a still-present player and orphaning the vacated slot.
|
|
52
54
|
let assignedSlots = 0;
|
|
53
55
|
let now = -1;
|
|
56
|
+
const secretAuthority = config.secrets === undefined
|
|
57
|
+
? undefined
|
|
58
|
+
: (0, secret_authority_js_1.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
|
+
});
|
|
54
68
|
// M5.5 snapshot acquisition state. The authority stores at most one validated
|
|
55
69
|
// snapshot and runs at most one donor transfer at a time.
|
|
56
70
|
const snapshotCadenceTicks = config.snapshotCadenceTicks ?? DEFAULT_SNAPSHOT_CADENCE_TICKS;
|
|
@@ -230,6 +244,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
230
244
|
const plan = catchUpPlan();
|
|
231
245
|
(0, errors_js_1.invariant)(plan !== 'unavailable', 'a restored room must have a catch-up plan for seated players', 'authority-room.restore-catchup');
|
|
232
246
|
sendSessionStart(playerId, slot);
|
|
247
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
233
248
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
234
249
|
}
|
|
235
250
|
}
|
|
@@ -249,6 +264,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
249
264
|
}
|
|
250
265
|
slotByPlayer.set(playerId, formerSlot);
|
|
251
266
|
sendSessionStart(playerId, formerSlot);
|
|
267
|
+
secretAuthority?.onPlayerReconnected(playerId, formerSlot);
|
|
252
268
|
sendCatchUpTo(playerId, rejoinPlan, 'rejoin');
|
|
253
269
|
return { accepted: true, slot: formerSlot, rejoined: true };
|
|
254
270
|
}
|
|
@@ -267,6 +283,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
267
283
|
slotByPlayer.set(playerId, slot);
|
|
268
284
|
everSlotByPlayer.set(playerId, slot);
|
|
269
285
|
sendSessionStart(playerId, slot);
|
|
286
|
+
secretAuthority?.onPlayerJoined(playerId, slot);
|
|
270
287
|
sendCatchUpTo(playerId, plan, 'join');
|
|
271
288
|
return { accepted: true, slot };
|
|
272
289
|
}
|
|
@@ -286,6 +303,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
286
303
|
return false;
|
|
287
304
|
spectators.add(playerId);
|
|
288
305
|
sendSessionStart(playerId, -1, 'spectator');
|
|
306
|
+
secretAuthority?.onSpectatorJoined(playerId);
|
|
289
307
|
sendCatchUpTo(playerId, plan, 'join');
|
|
290
308
|
return true;
|
|
291
309
|
}
|
|
@@ -312,8 +330,14 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
312
330
|
// client that fell behind the snapshot tick, so the stored snapshot rides
|
|
313
331
|
// along — the client applies it only when it is ahead of its own state.
|
|
314
332
|
const reconnectPlan = !historyReachesTickZero() && storedSnapshot !== undefined ? 'snapshot' : 'history';
|
|
333
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
315
334
|
sendCatchUpTo(playerId, reconnectPlan, 'reconnect');
|
|
316
335
|
}
|
|
336
|
+
function onPlayerSecretMessage(playerId, envelope) {
|
|
337
|
+
if (secretAuthority === undefined)
|
|
338
|
+
return;
|
|
339
|
+
secretAuthority.onPlayerRequest(playerId, slotByPlayer.get(playerId) ?? -1, envelope);
|
|
340
|
+
}
|
|
317
341
|
function onPlayerMessage(playerId, envelope) {
|
|
318
342
|
// Fail closed: unknown/malformed messages are dropped. Snapshot transfer
|
|
319
343
|
// envelopes are the OTHER frozen wire family (schemaVersion vs v field) —
|
|
@@ -574,7 +598,10 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
574
598
|
// Fixed roster for v1 (late-join/reconnect is M5). Dropping the mapping stops accepting the
|
|
575
599
|
// departed player's input; their slot stays reserved because `assignedSlots` never decreases,
|
|
576
600
|
// so the authority substitutes for it rather than handing the number to a new joiner.
|
|
601
|
+
const slot = slotByPlayer.get(playerId);
|
|
577
602
|
slotByPlayer.delete(playerId);
|
|
603
|
+
if (slot !== undefined)
|
|
604
|
+
secretAuthority?.onPlayerLeft(playerId, slot);
|
|
578
605
|
if (pendingTransfer?.donorId === playerId) {
|
|
579
606
|
failPendingTransfer('snapshot-donor-left');
|
|
580
607
|
}
|
|
@@ -589,6 +616,9 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
589
616
|
return;
|
|
590
617
|
}
|
|
591
618
|
now += 1;
|
|
619
|
+
for (const command of secretAuthority?.onTick(now, [...slotByPlayer.values()]) ?? []) {
|
|
620
|
+
authority.queueCommand(now, command);
|
|
621
|
+
}
|
|
592
622
|
authority.advance(now);
|
|
593
623
|
maintainSnapshotAcquisition();
|
|
594
624
|
if (authority.confirmedThrough < 0) {
|
|
@@ -639,6 +669,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
639
669
|
onPlayerJoin,
|
|
640
670
|
onSpectatorJoin,
|
|
641
671
|
onPlayerMessage,
|
|
672
|
+
onPlayerSecretMessage,
|
|
642
673
|
onPlayerLeave,
|
|
643
674
|
onPlayerReconnected,
|
|
644
675
|
onTick,
|
|
@@ -675,6 +706,7 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
675
706
|
if (plan === 'unavailable')
|
|
676
707
|
return false;
|
|
677
708
|
sendSessionStart(playerId, slot);
|
|
709
|
+
secretAuthority?.onPlayerReconnected(playerId, slot);
|
|
678
710
|
sendCatchUpTo(playerId, plan, 'resync');
|
|
679
711
|
return true;
|
|
680
712
|
},
|
|
@@ -700,5 +732,16 @@ function createDeterministicAuthorityRoom(transport, config) {
|
|
|
700
732
|
frames: authority.confirmedFrameRange(0, authority.confirmedThrough),
|
|
701
733
|
};
|
|
702
734
|
},
|
|
735
|
+
secretSnapshot() {
|
|
736
|
+
return secretAuthority?.snapshot();
|
|
737
|
+
},
|
|
703
738
|
};
|
|
704
739
|
}
|
|
740
|
+
function requireSecretAuthorityId(authorityId) {
|
|
741
|
+
(0, errors_js_1.invariant)(typeof authorityId === 'string' && authorityId.length > 0, 'secret authorityId is required', 'authority-room.secret-authority-id');
|
|
742
|
+
return authorityId;
|
|
743
|
+
}
|
|
744
|
+
function requireSecretCrypto(crypto) {
|
|
745
|
+
(0, errors_js_1.invariant)(crypto !== undefined, 'secret signing is unavailable', 'authority-room.secret-signing');
|
|
746
|
+
return crypto;
|
|
747
|
+
}
|
package/dist/cjs/browser.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.sortAabbsByStableId = exports.segmentsIntersect = exports.segmentSegment
|
|
|
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
7
|
exports.bytesToBase64 = exports.base64ToBytes = exports.exportPromotionSnapshot = exports.createSessionFromPromotionSnapshot = exports.createSyncplaySession = 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.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.applyDeterministicImpulses3D = exports.applyDeterministicImpulse3D = void 0;
|
|
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;
|
|
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 = 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; } });
|
|
@@ -268,3 +268,17 @@ Object.defineProperty(exports, "createDeterministicSparseSet", { enumerable: tru
|
|
|
268
268
|
var system_lifecycle_js_1 = require("./system-lifecycle.js");
|
|
269
269
|
Object.defineProperty(exports, "createDeterministicLifecycleRunner", { enumerable: true, get: function () { return system_lifecycle_js_1.createDeterministicLifecycleRunner; } });
|
|
270
270
|
Object.defineProperty(exports, "summarizeDeterministicLifecycleCounters", { enumerable: true, get: function () { return system_lifecycle_js_1.summarizeDeterministicLifecycleCounters; } });
|
|
271
|
+
var secret_config_js_1 = require("./secret-config.js");
|
|
272
|
+
Object.defineProperty(exports, "syncplaySecretConfigHash", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretConfigHash; } });
|
|
273
|
+
Object.defineProperty(exports, "syncplaySecretLimits", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretLimits; } });
|
|
274
|
+
Object.defineProperty(exports, "validateSyncplaySecretSystemsConfig", { enumerable: true, get: function () { return secret_config_js_1.validateSyncplaySecretSystemsConfig; } });
|
|
275
|
+
var secret_protocol_js_1 = require("./secret-protocol.js");
|
|
276
|
+
Object.defineProperty(exports, "decodeSyncplaySecretPrivateMessage", { enumerable: true, get: function () { return secret_protocol_js_1.decodeSyncplaySecretPrivateMessage; } });
|
|
277
|
+
Object.defineProperty(exports, "isSyncplaySecretCommand", { enumerable: true, get: function () { return secret_protocol_js_1.isSyncplaySecretCommand; } });
|
|
278
|
+
Object.defineProperty(exports, "syncplaySecretProtocol", { enumerable: true, get: function () { return secret_protocol_js_1.syncplaySecretProtocol; } });
|
|
279
|
+
var secret_crypto_js_1 = require("./secret-crypto.js");
|
|
280
|
+
Object.defineProperty(exports, "createBrowserSyncplaySecretCrypto", { enumerable: true, get: function () { return secret_crypto_js_1.createBrowserSyncplaySecretCrypto; } });
|
|
281
|
+
Object.defineProperty(exports, "verifySyncplaySignedReceipt", { enumerable: true, get: function () { return secret_crypto_js_1.verifySyncplaySignedReceipt; } });
|
|
282
|
+
var secret_client_js_1 = require("./secret-client.js");
|
|
283
|
+
Object.defineProperty(exports, "createSyncplaySecretEventReader", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretEventReader; } });
|
|
284
|
+
Object.defineProperty(exports, "createSyncplaySecretsClient", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretsClient; } });
|
|
@@ -1276,10 +1276,7 @@ function physicsCategory(bodies, requestedFrames, executedFrames, enforceThresho
|
|
|
1276
1276
|
&& stack.settledDynamicBodies === stack.stackLayers,
|
|
1277
1277
|
stackRollbackEquivalent: stack.rollbackEquivalent,
|
|
1278
1278
|
stackAuthoringOrderStable: stack.stableUnderAuthoringOrderPermutation,
|
|
1279
|
-
|
|
1280
|
-
// (floor-average friction, max restitution); the bounce/friction velocity
|
|
1281
|
-
// magnitudes come from the sequential-impulse solver, not the legacy pusher.
|
|
1282
|
-
materialResponseCovered: materials.combinedFriction === 3
|
|
1279
|
+
materialResponseCovered: Math.abs(materials.combinedFriction - 0.28284271) < 1e-6
|
|
1283
1280
|
&& materials.combinedRestitution === 7
|
|
1284
1281
|
&& materials.normalVelocityAfterBounce > 0
|
|
1285
1282
|
&& materials.tangentVelocityAfterFrictionY < 10
|
|
@@ -625,11 +625,18 @@ const engineParityMatrixEntries = [
|
|
|
625
625
|
'packages/syncplay/src/physics3d-solver.ts',
|
|
626
626
|
'packages/syncplay/src/physics3d-toi.ts',
|
|
627
627
|
'packages/syncplay/src/physics3d-joints.ts',
|
|
628
|
+
'packages/syncplay/src/physics3d-mass.ts',
|
|
629
|
+
'packages/syncplay/src/physics3d-heightfield.ts',
|
|
628
630
|
'packages/syncplay/tests/physics2d.test.ts',
|
|
629
631
|
'packages/syncplay/tests/physics3d-solver.test.ts',
|
|
630
632
|
'packages/syncplay/tests/physics3d-joints.test.ts',
|
|
631
633
|
'packages/syncplay/tests/physics3d-ccd.test.ts',
|
|
632
634
|
'packages/syncplay/tests/physics3d-toi.test.ts',
|
|
635
|
+
'packages/syncplay/tests/physics3d-mass.test.ts',
|
|
636
|
+
'packages/syncplay/tests/physics3d-materials.test.ts',
|
|
637
|
+
'packages/syncplay/tests/physics3d-heightfield.test.ts',
|
|
638
|
+
'packages/syncplay/tests/physics3d-rotation-convention.test.ts',
|
|
639
|
+
'packages/syncplay/tests/physics3d-mesh-onesided.test.ts',
|
|
633
640
|
'packages/syncplay/tests/engine-complete.test.ts',
|
|
634
641
|
'packages/syncplay/src/physics3d-vehicle.ts',
|
|
635
642
|
'packages/syncplay/tests/physics3d-vehicle.test.ts',
|
|
@@ -640,16 +647,17 @@ const engineParityMatrixEntries = [
|
|
|
640
647
|
'packages/syncplay/src/physics3d-hull.ts',
|
|
641
648
|
'packages/syncplay/tests/physics3d-hull.test.ts',
|
|
642
649
|
'TGS-Soft (sub-stepped soft-constraint) rigid solver: box3d-class stable stacking, tumbling, sleeping, axis locks, joints (ball/hinge/weld/rope/prismatic/spring/grab), opt-in TOI-bound CCD, compound colliders, orientation-aware queries',
|
|
643
|
-
'extreme-speed CCD (box3d model, src/physics3d-toi.ts): an opt-in ccd body whose per-tick motion exceeds half its smallest extent has its exact first time-of-impact found by GJK conservative advancement; it is advanced there and the impact resolved as a perfectly-inelastic collision
|
|
650
|
+
'extreme-speed CCD (box3d model, src/physics3d-toi.ts): an opt-in ccd body whose per-tick motion exceeds half its smallest extent has its exact first time-of-impact found by GJK conservative advancement against convex shapes and culled heightfield cell triangles; it is advanced there and the impact resolved as a perfectly-inelastic collision with momentum transfer and a TOI position clamp. physics3d-ccd.test.ts and physics3d-heightfield.test.ts cover finite-at-vx60, heightfield-cliff no-tunnel, rollback, and permutation equivalence',
|
|
644
651
|
'stacking stability gated in CI: tower-15 (<1° tilt), 50:1 mass-ratio column, pyramid, kick-recovery, and a 20-layer stack all meet drift/penetration/up-alignment thresholds with rollback + authoring-order-permutation equivalence',
|
|
645
652
|
'convex-hull collision shape (4–16 verts): deterministic supporting-plane preprocessing, SAT+clip hull-vs-hull/box, closest-point hull-vs-sphere/capsule, support-sample hull-vs-static-mesh, exact ray/overlap/shape-cast queries; golden hull-pair battery pinned',
|
|
646
653
|
'cone-twist ball joint (swing cone + twist limits via fixed-point cosine) drives a 6-body ragdoll that settles kinetically and stays articulated; rigid-wheel vehicle recipe (sphere wheels + hinge motors) certified alongside the raycast controller',
|
|
647
654
|
'builtin-rigid physics-cert lane passes all 4 determinism gates; contact/joint caches are checksummed rollback state',
|
|
655
|
+
'builtin-rigid@2 certifies true hull/compound inertia and center-of-mass integration, geometric-mean friction, rolling resistance, static heightfields with CCD/query parity, and active spherical motor/orientation-spring rows',
|
|
648
656
|
'raycast-wheel vehicle controller (stepDeterministicVehicle3D): spring-damper suspension, friction-circle drive/steer/brake, fixed-point steering trig, moving-ground reaction (slip relative to the contact body; equal-and-opposite reaction on dynamic ground)',
|
|
649
657
|
'per-world static AABB-tree query index: raycast/overlap/shape-cast are O(log n + hits) with bit-identical results; batched applyDeterministicImpulses3D applies a frame of controller impulses in one canonical rebuild',
|
|
650
658
|
'pooled narrow-phase/constraint scratch (scalarized box face-clip over ping-pong clip slots, step-scoped constraint pool); prismatic/breakable/collision-suppressed joints, a soft frequencyHz/dampingRatio spring joint (breakable/disable-collision), and per-body gravityScale — joint breaks are sticky checksummed cache state',
|
|
651
659
|
'kinematic bodies integrate their authored velocity (linear + angular) inside the solver, drive the dynamic bodies they touch through the contact relative-velocity term, absorb the equal-and-opposite reaction (zero inverse mass), and wake the sleeping piles they push; a kinematic mesh is therefore a moving platform / conveyor collider. A dynamic mesh is rejected at world creation',
|
|
652
|
-
'one-sided mesh contacts resolve penetration for zero-radius shapes
|
|
660
|
+
'one-sided mesh and heightfield contacts resolve penetration for zero-radius shapes while rejecting genuine back-face approaches; a shallow solver-creep recovery band keeps boxes/hulls resting without ejecting bodies from below',
|
|
653
661
|
'compound colliders accept hull children (packages/syncplay/src/collider-cooking.ts): an offline V-HACD-shaped cooker voxelizes a concave triangle soup, splits it on the axis-aligned plane that best reduces concavity, and emits a compound of ≤16-vert convex hulls that the runtime validates with buildSolverHull. The cook is order-invariant and byte-reproducible (exact integer-lattice convex hull, lexicographic tie-breaks); no decomposition code runs in a simulation step',
|
|
654
662
|
],
|
|
655
663
|
cliProofs: [
|
|
@@ -663,6 +671,7 @@ const engineParityMatrixEntries = [
|
|
|
663
671
|
gaps: [
|
|
664
672
|
'Mesh-vs-mesh collision remains unsupported by design: a mesh collider is static or kinematic, and a dynamic concave body must be cooked to a compound of hulls (collider:cook).',
|
|
665
673
|
'Kinematic bodies resolve contacts discretely — a kinematic mesh driven faster than its own extent per tick can pass through a thin body (no CCD for kinematic; ccd is a dynamic-body opt-in).',
|
|
674
|
+
'Dynamic ccd sweeps box, sphere, capsule, hull, and heightfield targets; triangle-soup mesh and compound targets remain discrete-only.',
|
|
666
675
|
'Convex decomposition is offline only: the cooked hull set is conservative (it contains the source, inflated by up to one voxel of the chosen resolution) and hulls stay capped at 16 verts.',
|
|
667
676
|
],
|
|
668
677
|
nextCliWork: [
|
|
@@ -767,7 +767,7 @@ function runPhysicsCategory(config) {
|
|
|
767
767
|
&& physics3DQueries.overlapCovered
|
|
768
768
|
&& physics3DQueries.shapeCastCovered,
|
|
769
769
|
physics3DMaterialResponse: physics3DMaterials.rollbackEquivalent
|
|
770
|
-
&& physics3DMaterials.combinedFriction
|
|
770
|
+
&& Math.abs(physics3DMaterials.combinedFriction - 0.28284271) < 1e-6
|
|
771
771
|
&& physics3DMaterials.combinedRestitution === 7
|
|
772
772
|
&& physics3DMaterials.normalVelocityAfterBounce < 0,
|
|
773
773
|
physics3DDetailedQueriesAndContacts: physics3DDetails.rollbackEquivalent
|
package/dist/cjs/index.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.createDeterministicCommandTimelineSnapshot = exports.createDeterministic
|
|
|
5
5
|
exports.createDeterministicLocalAuthorityCorrectionEnvelope = exports.createDeterministicLocalAuthorityCommandBundleEnvelope = exports.createDeterministicLocalAuthorityAckEnvelope = exports.applyDeterministicLocalAuthorityInputBundleEnvelope = exports.applyDeterministicLocalAuthorityCommandBundleEnvelope = exports.createDeterministicLocalAuthorityRuntime = exports.validateDeterministicLagCompensatedShot3D = exports.validateDeterministicAuthoritativeLagCompensatedShot3D = exports.rewindDeterministicLagCompensatedTargets3D = exports.createDeterministicLagCompensation3DHistory = exports.buildDeterministicLagCompensatedHitboxProxies3D = exports.sortDeterministicInputTimelineFlags = exports.evaluateDeterministicInputTimelineResult = exports.accumulateDeterministicInputTimelineState = exports.createInputAuthority = exports.filterDeterministicEventsForPlayer = exports.createDeterministicEventTimeline = exports.SyncplayError = 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.stableDeterministicEngineCompleteJson = exports.runDeterministicEngineCompleteSuite = exports.engineCompleteExecutionProofHash = exports.createDeterministicEventEffectsAdapter = exports.createEcsLiteWorld = exports.verifyDeterministicSessionRuntimeReplay = exports.createDeterministicSessionReplayBundle = exports.createDeterministicSessionCore = exports.stepTopDownBody = exports.stepKccLiteBody = exports.quantizeFpsAim = exports.detectVerticalCcdHit = exports.cookStaticColliders = exports.combineDeterministicPhysicsMaterial = exports.verifyDeterministicConfigBundle = exports.summarizeDeterministicConfigBundleFailureReasons = exports.evaluateDeterministicConfigBundleParityResult = exports.cookDeterministicConfigBundle = exports.evaluateDeterministicCommandTimelineMigrationResult = exports.deterministicPayloadMatchesFields = void 0;
|
|
6
6
|
exports.queryDeterministicPhysicsShapeOverlap2D = exports.queryDeterministicPhysicsAabb2D = exports.nearestDeterministicPhysicsHit2D = exports.createDeterministicPhysicsWorld2D = exports.circleCastDeterministicPhysics2D = 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.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 = exports.createDeterministicLocalAuthoritySnapshotOfferEnvelope = exports.createDeterministicLocalAuthoritySnapshotEnvelope = exports.createDeterministicLocalAuthoritySnapshotCompleteEnvelope = exports.createDeterministicLocalAuthoritySnapshotChunkEnvelope = exports.createDeterministicLocalAuthorityInputBundleEnvelope = void 0;
|
|
7
7
|
exports.diffDeterministicReports = exports.createProtocolRollbackReport = exports.verifyScoredReplay = exports.stableFrameDumpJson = exports.stableFrameDiffJson = exports.inspectReplayFrameWithDescriptor = exports.inspectReplayFrame = exports.explainReplayChecksum = exports.diffReplayFrames = exports.verifyReplayScoreAgainstTrustedRuntime = exports.verifyDeterministicSelfContainedFrameRuntimeReplayBundle = exports.verifyDeterministicReplayBundle = exports.verifyDeterministicFrameRuntimeReplayBundle = exports.sliceDeterministicReplayBundle = exports.minimizeDeterministicReplayDesyncBundle = exports.minimizeDeterministicReplayDesync = exports.createDeterministicReplayRuntimeManifest = exports.createDeterministicReplayDebugTables = exports.createDeterministicReplayBundle = exports.verifyReplayWithDescriptor = exports.verifyReplay = exports.DeterministicRandom = exports.interpolateDeterministicRenderVec2 = exports.interpolateDeterministicRenderNumber = exports.createDeterministicR3fRenderAdapter = exports.createDeterministicR3fDebugOverlay = exports.stepDeterministicVehicle3D = exports.runDeterministicTower3DScenario = exports.runDeterministicStackingSuite3D = exports.runDeterministicPyramid3DScenario = exports.runDeterministicMassRatio3DScenario = exports.runDeterministicKickRecovery3DScenario = 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.applyDeterministicImpulses3D = exports.applyDeterministicImpulse3D = exports.stepDeterministicPhysicsWorld2D = exports.raycastDeterministicPhysics2D = exports.raycastAllDeterministicPhysics2D = void 0;
|
|
8
|
-
exports.tickCooldown = exports.scheduleFrameEvent = exports.popDueFrameEvents = exports.millisecondsToFrames = exports.framesToMilliseconds = exports.createCooldown = 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.createSyncplayOfflineRuntime = exports.stableDeterministicDslSchemaJson = exports.parseDeterministicSchemaDsl = exports.deterministicDslMaxPlayers = exports.createDeterministicBinarySerializer = exports.proveSchemaBinaryRoundTrip = exports.generateSchemaTypes = exports.generateSchemaBinarySource = exports.createDeterministicDescriptorBinarySchema = exports.createSchemaArtifacts = exports.stableDescriptorId = exports.defineSyncplayGame = exports.withRuntimeDeterminismGuards = exports.createDeterministicRollbackHistory = exports.stableReportJson = exports.stableReportDiffJson = exports.renderDeterministicReportHtml = void 0;
|
|
8
|
+
exports.createSyncplaySecretsClient = exports.createSyncplaySecretEventReader = exports.createSyncplaySecretAuthority = exports.verifySyncplaySignedReceipt = exports.createBrowserSyncplaySecretCrypto = exports.syncplaySecretProtocol = exports.isSyncplaySecretCommand = exports.decodeSyncplaySecretPrivateMessage = exports.validateSyncplaySecretSystemsConfig = exports.syncplaySecretLimits = exports.syncplaySecretConfigHash = exports.tickCooldown = exports.scheduleFrameEvent = exports.popDueFrameEvents = exports.millisecondsToFrames = exports.framesToMilliseconds = exports.createCooldown = 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.createSyncplayOfflineRuntime = exports.stableDeterministicDslSchemaJson = exports.parseDeterministicSchemaDsl = exports.deterministicDslMaxPlayers = exports.createDeterministicBinarySerializer = exports.proveSchemaBinaryRoundTrip = exports.generateSchemaTypes = exports.generateSchemaBinarySource = exports.createDeterministicDescriptorBinarySchema = exports.createSchemaArtifacts = exports.stableDescriptorId = exports.defineSyncplayGame = exports.withRuntimeDeterminismGuards = exports.createDeterministicRollbackHistory = exports.stableReportJson = exports.stableReportDiffJson = exports.renderDeterministicReportHtml = 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; } });
|
|
@@ -300,3 +300,19 @@ Object.defineProperty(exports, "millisecondsToFrames", { enumerable: true, get:
|
|
|
300
300
|
Object.defineProperty(exports, "popDueFrameEvents", { enumerable: true, get: function () { return timers_js_1.popDueFrameEvents; } });
|
|
301
301
|
Object.defineProperty(exports, "scheduleFrameEvent", { enumerable: true, get: function () { return timers_js_1.scheduleFrameEvent; } });
|
|
302
302
|
Object.defineProperty(exports, "tickCooldown", { enumerable: true, get: function () { return timers_js_1.tickCooldown; } });
|
|
303
|
+
var secret_config_js_1 = require("./secret-config.js");
|
|
304
|
+
Object.defineProperty(exports, "syncplaySecretConfigHash", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretConfigHash; } });
|
|
305
|
+
Object.defineProperty(exports, "syncplaySecretLimits", { enumerable: true, get: function () { return secret_config_js_1.syncplaySecretLimits; } });
|
|
306
|
+
Object.defineProperty(exports, "validateSyncplaySecretSystemsConfig", { enumerable: true, get: function () { return secret_config_js_1.validateSyncplaySecretSystemsConfig; } });
|
|
307
|
+
var secret_protocol_js_1 = require("./secret-protocol.js");
|
|
308
|
+
Object.defineProperty(exports, "decodeSyncplaySecretPrivateMessage", { enumerable: true, get: function () { return secret_protocol_js_1.decodeSyncplaySecretPrivateMessage; } });
|
|
309
|
+
Object.defineProperty(exports, "isSyncplaySecretCommand", { enumerable: true, get: function () { return secret_protocol_js_1.isSyncplaySecretCommand; } });
|
|
310
|
+
Object.defineProperty(exports, "syncplaySecretProtocol", { enumerable: true, get: function () { return secret_protocol_js_1.syncplaySecretProtocol; } });
|
|
311
|
+
var secret_crypto_js_1 = require("./secret-crypto.js");
|
|
312
|
+
Object.defineProperty(exports, "createBrowserSyncplaySecretCrypto", { enumerable: true, get: function () { return secret_crypto_js_1.createBrowserSyncplaySecretCrypto; } });
|
|
313
|
+
Object.defineProperty(exports, "verifySyncplaySignedReceipt", { enumerable: true, get: function () { return secret_crypto_js_1.verifySyncplaySignedReceipt; } });
|
|
314
|
+
var secret_authority_js_1 = require("./secret-authority.js");
|
|
315
|
+
Object.defineProperty(exports, "createSyncplaySecretAuthority", { enumerable: true, get: function () { return secret_authority_js_1.createSyncplaySecretAuthority; } });
|
|
316
|
+
var secret_client_js_1 = require("./secret-client.js");
|
|
317
|
+
Object.defineProperty(exports, "createSyncplaySecretEventReader", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretEventReader; } });
|
|
318
|
+
Object.defineProperty(exports, "createSyncplaySecretsClient", { enumerable: true, get: function () { return secret_client_js_1.createSyncplaySecretsClient; } });
|
|
@@ -10,6 +10,7 @@ function createInputAuthority(config) {
|
|
|
10
10
|
const maxFutureTicks = config.maxFutureTicks ?? Number.POSITIVE_INFINITY;
|
|
11
11
|
const retainedConfirmedFrames = config.retainedConfirmedFrames ?? 512;
|
|
12
12
|
const pending = new Map();
|
|
13
|
+
const pendingCommands = new Map();
|
|
13
14
|
const lastConfirmedInput = Array.from({ length: playerCount }, () => config.neutralInput);
|
|
14
15
|
const retained = new Map();
|
|
15
16
|
let confirmedThrough = -1;
|
|
@@ -22,7 +23,7 @@ function createInputAuthority(config) {
|
|
|
22
23
|
const frame = frames[index];
|
|
23
24
|
(0, errors_js_1.invariant)(Number.isInteger(frame.tick) && (index === 0 || frame.tick === frames[index - 1].tick + 1), 'restoreFrom frames must be tick-contiguous', 'input-authority.restore-gap');
|
|
24
25
|
(0, errors_js_1.invariant)(frame.inputs.length === playerCount, 'restoreFrom frame inputs must cover every player slot', 'input-authority.restore-inputs');
|
|
25
|
-
retained.set(frame.tick, frame);
|
|
26
|
+
retained.set(frame.tick, (0, canonical_js_1.cloneCanonical)(frame));
|
|
26
27
|
}
|
|
27
28
|
(0, errors_js_1.invariant)(frames[frames.length - 1].tick === restoredThrough, 'restoreFrom frames must end at the restored frontier', 'input-authority.restore-frontier');
|
|
28
29
|
earliestRetained = frames[0].tick;
|
|
@@ -75,11 +76,16 @@ function createInputAuthority(config) {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
pending.delete(tick);
|
|
79
|
+
const commands = (pendingCommands.get(tick) ?? [])
|
|
80
|
+
.map((command) => (0, canonical_js_1.cloneCanonical)(command))
|
|
81
|
+
.sort(compareAuthorityCommands);
|
|
82
|
+
pendingCommands.delete(tick);
|
|
78
83
|
const frame = {
|
|
79
84
|
tick,
|
|
80
85
|
inputs,
|
|
81
86
|
substitutedSlots,
|
|
82
|
-
|
|
87
|
+
...(commands.length === 0 ? {} : { commands }),
|
|
88
|
+
checksum: String((0, canonical_js_1.defaultChecksum)(commands.length === 0 ? { tick, inputs } : { tick, inputs, commands })),
|
|
83
89
|
};
|
|
84
90
|
retained.set(tick, frame);
|
|
85
91
|
if (earliestRetained === -1) {
|
|
@@ -116,6 +122,13 @@ function createInputAuthority(config) {
|
|
|
116
122
|
return confirmedThrough;
|
|
117
123
|
},
|
|
118
124
|
receiveInput,
|
|
125
|
+
queueCommand(tick, command) {
|
|
126
|
+
(0, errors_js_1.invariant)(Number.isInteger(tick) && tick >= 0, 'command tick must be a non-negative integer', 'input-authority.command-tick');
|
|
127
|
+
(0, errors_js_1.invariant)(tick > confirmedThrough, `command tick ${tick} is already confirmed`, 'input-authority.command-confirmed');
|
|
128
|
+
const commands = pendingCommands.get(tick) ?? [];
|
|
129
|
+
commands.push((0, canonical_js_1.cloneCanonical)(command));
|
|
130
|
+
pendingCommands.set(tick, commands);
|
|
131
|
+
},
|
|
119
132
|
advance,
|
|
120
133
|
get earliestRetainedTick() {
|
|
121
134
|
return earliestRetained;
|
|
@@ -148,3 +161,22 @@ function createInputAuthority(config) {
|
|
|
148
161
|
},
|
|
149
162
|
};
|
|
150
163
|
}
|
|
164
|
+
function compareAuthorityCommands(left, right) {
|
|
165
|
+
const leftRecord = isCanonicalRecord(left) ? left : undefined;
|
|
166
|
+
const rightRecord = isCanonicalRecord(right) ? right : undefined;
|
|
167
|
+
const leftSystem = typeof leftRecord?.systemId === 'string' ? leftRecord.systemId : '';
|
|
168
|
+
const rightSystem = typeof rightRecord?.systemId === 'string' ? rightRecord.systemId : '';
|
|
169
|
+
const systemOrder = compareCanonicalText(leftSystem, rightSystem);
|
|
170
|
+
if (systemOrder !== 0)
|
|
171
|
+
return systemOrder;
|
|
172
|
+
const leftRequest = typeof leftRecord?.requestId === 'string' ? leftRecord.requestId : '';
|
|
173
|
+
const rightRequest = typeof rightRecord?.requestId === 'string' ? rightRecord.requestId : '';
|
|
174
|
+
const requestOrder = compareCanonicalText(leftRequest, rightRequest);
|
|
175
|
+
return requestOrder !== 0 ? requestOrder : compareCanonicalText((0, canonical_js_1.canonicalStringify)(left), (0, canonical_js_1.canonicalStringify)(right));
|
|
176
|
+
}
|
|
177
|
+
function compareCanonicalText(left, right) {
|
|
178
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
179
|
+
}
|
|
180
|
+
function isCanonicalRecord(value) {
|
|
181
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
182
|
+
}
|