@series-inc/rundot-syncplay 5.23.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 +53 -12
- package/dist/authority-room.d.ts +11 -0
- package/dist/authority-room.js +43 -0
- package/dist/browser.d.ts +11 -1
- package/dist/browser.js +5 -0
- package/dist/checksum-basis.d.ts +4 -0
- package/dist/checksum-basis.js +17 -0
- package/dist/cjs/authority-room.js +43 -0
- package/dist/cjs/browser.js +18 -2
- package/dist/cjs/checksum-basis.js +22 -0
- package/dist/cjs/collider-cooking.js +834 -0
- package/dist/cjs/deterministic-session.js +2 -0
- package/dist/cjs/engine-complete.js +1 -4
- package/dist/cjs/engine-parity-matrix.js +41 -9
- package/dist/cjs/engine-parity.js +2 -2
- package/dist/cjs/index.js +24 -2
- package/dist/cjs/input-authority.js +34 -2
- package/dist/cjs/instant-replay.js +4 -9
- 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/movement3d.js +17 -1
- package/dist/cjs/networked-client.js +70 -20
- package/dist/cjs/node.js +23 -1
- package/dist/cjs/offline-session.js +52 -24
- package/dist/cjs/physics-cert.js +8 -3
- package/dist/cjs/physics3d-heightfield.js +51 -0
- package/dist/cjs/physics3d-hull.js +381 -0
- package/dist/cjs/physics3d-joints.js +255 -49
- package/dist/cjs/physics3d-mass.js +131 -0
- package/dist/cjs/physics3d-recipes.js +197 -0
- package/dist/cjs/physics3d-shared.js +11 -1
- package/dist/cjs/physics3d-solver.js +845 -98
- package/dist/cjs/physics3d-stacking.js +154 -0
- package/dist/cjs/physics3d-toi.js +274 -0
- package/dist/cjs/physics3d-vehicle.js +35 -10
- package/dist/cjs/physics3d.js +601 -38
- package/dist/cjs/replay.js +25 -1
- package/dist/cjs/runtime-support.js +71 -0
- 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/session-promotion.js +5 -5
- package/dist/cjs/synctest.js +49 -0
- package/dist/cjs/testing.js +4 -1
- package/dist/cjs/tools/static-checker.js +61 -13
- package/dist/cli.js +193 -48
- package/dist/collider-cooking.d.ts +141 -0
- package/dist/collider-cooking.js +829 -0
- package/dist/deterministic-primitives.d.ts +1 -0
- package/dist/deterministic-session.js +2 -0
- package/dist/engine-complete.js +1 -4
- package/dist/engine-parity-matrix.js +41 -9
- package/dist/engine-parity.js +2 -2
- package/dist/index.d.ts +12 -1
- package/dist/index.js +6 -0
- package/dist/input-authority.d.ts +3 -0
- package/dist/input-authority.js +35 -3
- package/dist/instant-replay.d.ts +2 -2
- package/dist/instant-replay.js +4 -9
- 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/movement3d.js +18 -2
- package/dist/networked-client.d.ts +8 -0
- package/dist/networked-client.js +70 -20
- package/dist/node.d.ts +7 -0
- package/dist/node.js +6 -0
- package/dist/offline-session.js +53 -25
- package/dist/physics-cert.js +8 -3
- package/dist/physics3d-heightfield.d.ts +17 -0
- package/dist/physics3d-heightfield.js +47 -0
- package/dist/physics3d-hull.d.ts +53 -0
- package/dist/physics3d-hull.js +376 -0
- package/dist/physics3d-joints.d.ts +13 -1
- package/dist/physics3d-joints.js +256 -50
- package/dist/physics3d-mass.d.ts +11 -0
- package/dist/physics3d-mass.js +126 -0
- package/dist/physics3d-recipes.d.ts +29 -0
- package/dist/physics3d-recipes.js +193 -0
- package/dist/physics3d-shared.d.ts +1 -0
- package/dist/physics3d-shared.js +10 -1
- package/dist/physics3d-solver.d.ts +78 -2
- package/dist/physics3d-solver.js +842 -98
- package/dist/physics3d-stacking.d.ts +26 -0
- package/dist/physics3d-stacking.js +147 -0
- package/dist/physics3d-toi.d.ts +79 -0
- package/dist/physics3d-toi.js +267 -0
- package/dist/physics3d-vehicle.js +35 -10
- package/dist/physics3d.d.ts +28 -3
- package/dist/physics3d.js +601 -40
- package/dist/replay.d.ts +5 -0
- package/dist/replay.js +25 -1
- package/dist/reports.d.ts +6 -1
- package/dist/runtime-support.d.ts +60 -0
- package/dist/runtime-support.js +66 -0
- 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/session-promotion.js +5 -5
- package/dist/synctest.d.ts +1 -1
- package/dist/synctest.js +49 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +1 -0
- package/dist/tools/static-checker.d.ts +11 -0
- package/dist/tools/static-checker.js +60 -13
- package/dist/types.d.ts +21 -2
- package/package.json +8 -3
|
@@ -6,6 +6,7 @@ const errors_js_1 = require("./errors.js");
|
|
|
6
6
|
const local_authority_wire_js_1 = require("./local-authority-wire.js");
|
|
7
7
|
const session_snapshot_js_1 = require("./session-snapshot.js");
|
|
8
8
|
const session_wire_js_1 = require("./session-wire.js");
|
|
9
|
+
const secret_client_js_1 = require("./secret-client.js");
|
|
9
10
|
const DEFAULT_MSG_TYPE = 'rdm';
|
|
10
11
|
const DEFAULT_MAX_PREDICTION = 8;
|
|
11
12
|
const DEFAULT_CHECKSUM_CADENCE_TICKS = 120;
|
|
@@ -82,6 +83,9 @@ function createNetworkedSyncplayClient(options) {
|
|
|
82
83
|
const ownInputs = new Map();
|
|
83
84
|
const confirmed = new Map();
|
|
84
85
|
let lastConfirmedBySlot = [];
|
|
86
|
+
let confirmationQueue;
|
|
87
|
+
let secretFailure;
|
|
88
|
+
let secrets;
|
|
85
89
|
// M5.5 snapshot late-join state: identities from session-start (descriptor)
|
|
86
90
|
// and the session config (schema), the donor upload queue, and the joiner's
|
|
87
91
|
// transfer collector.
|
|
@@ -147,12 +151,25 @@ function createNetworkedSyncplayClient(options) {
|
|
|
147
151
|
}
|
|
148
152
|
else if (message.kind === 'confirmed-input') {
|
|
149
153
|
observeAuthorityTick(message.authorityTick, wallClock());
|
|
150
|
-
|
|
154
|
+
receiveConfirmedFrames(message.frames);
|
|
151
155
|
}
|
|
152
156
|
else if (message.kind === 'time-sync-pong') {
|
|
153
157
|
handleTimeSyncPong(message.nonce, message.authorityTick);
|
|
154
158
|
}
|
|
155
159
|
});
|
|
160
|
+
// Attach the public channel first: both channels may have buffered their
|
|
161
|
+
// join-time greetings, and secret-start is bound to the slot established by
|
|
162
|
+
// session-start. Flushing the private channel first would reject an honest
|
|
163
|
+
// greeting against the initial slot -1.
|
|
164
|
+
secrets = (0, secret_client_js_1.createSyncplaySecretsClient)({
|
|
165
|
+
transport: options.transport,
|
|
166
|
+
connection: () => ({
|
|
167
|
+
playerId: options.playerId ?? options.transport.playerId,
|
|
168
|
+
slot,
|
|
169
|
+
role,
|
|
170
|
+
}),
|
|
171
|
+
...(options.secretCrypto === undefined ? {} : { crypto: options.secretCrypto }),
|
|
172
|
+
});
|
|
156
173
|
// ── G1: authority-clock estimation + drift-corrected timescale ──
|
|
157
174
|
function handleTimeSyncPong(nonce, authorityTick) {
|
|
158
175
|
const sentAtMs = pendingPings.get(nonce);
|
|
@@ -278,7 +295,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
278
295
|
function inputForSlot(targetSlot, tick) {
|
|
279
296
|
const conf = confirmed.get(tick);
|
|
280
297
|
if (conf !== undefined) {
|
|
281
|
-
return conf[targetSlot];
|
|
298
|
+
return conf.inputs[targetSlot];
|
|
282
299
|
}
|
|
283
300
|
if (targetSlot === slot) {
|
|
284
301
|
// Scheduled by the cursor (possibly delay-shifted). A tick with no
|
|
@@ -323,7 +340,8 @@ function createNetworkedSyncplayClient(options) {
|
|
|
323
340
|
inputs.push(encoded);
|
|
324
341
|
active.setInput(s, decodeInputSafe(encoded));
|
|
325
342
|
}
|
|
326
|
-
timeline[tick] = inputs;
|
|
343
|
+
timeline[tick] = { inputs, commands: [] };
|
|
344
|
+
active.recordCommands([]);
|
|
327
345
|
active.stepFrames(1);
|
|
328
346
|
predictedThrough = tick + 1;
|
|
329
347
|
}
|
|
@@ -340,11 +358,15 @@ function createNetworkedSyncplayClient(options) {
|
|
|
340
358
|
inputs.push(encoded);
|
|
341
359
|
active.setInput(s, decodeInputSafe(encoded));
|
|
342
360
|
}
|
|
343
|
-
|
|
361
|
+
const commands = confirmed.get(tick)?.commands ?? [];
|
|
362
|
+
timeline[tick] = { inputs, commands };
|
|
363
|
+
active.recordCommands(commands);
|
|
344
364
|
active.stepFrames(1);
|
|
345
365
|
}
|
|
346
366
|
}
|
|
347
367
|
function pump() {
|
|
368
|
+
if (secretFailure)
|
|
369
|
+
throw secretFailure;
|
|
348
370
|
if (session === undefined) {
|
|
349
371
|
return;
|
|
350
372
|
}
|
|
@@ -354,6 +376,8 @@ function createNetworkedSyncplayClient(options) {
|
|
|
354
376
|
}
|
|
355
377
|
}
|
|
356
378
|
function pumpPaced(nowMs) {
|
|
379
|
+
if (secretFailure)
|
|
380
|
+
throw secretFailure;
|
|
357
381
|
flushOutgoingTransfer();
|
|
358
382
|
maybeSendPing(nowMs);
|
|
359
383
|
if (session === undefined || frameMs <= 0) {
|
|
@@ -394,20 +418,43 @@ function createNetworkedSyncplayClient(options) {
|
|
|
394
418
|
return '';
|
|
395
419
|
}
|
|
396
420
|
active.restoreToFrame(frame);
|
|
397
|
-
return
|
|
421
|
+
return active.getWireStateChecksum(frame);
|
|
422
|
+
}
|
|
423
|
+
function receiveConfirmedFrames(frames) {
|
|
424
|
+
const containsSecretCommands = frames.some((frame) => (frame.commands ?? []).some((command) => (command !== null && typeof command === 'object' && !Array.isArray(command)
|
|
425
|
+
&& typeof command.kind === 'string'
|
|
426
|
+
&& command.kind.startsWith('syncplay.'))));
|
|
427
|
+
if (!containsSecretCommands && confirmationQueue === undefined) {
|
|
428
|
+
applyConfirmedFrames(frames);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const prior = confirmationQueue ?? Promise.resolve();
|
|
432
|
+
const next = prior.then(async () => {
|
|
433
|
+
for (const frame of frames)
|
|
434
|
+
await secrets.acceptConfirmedCommands(frame.commands ?? []);
|
|
435
|
+
applyConfirmedFrames(frames);
|
|
436
|
+
});
|
|
437
|
+
confirmationQueue = next;
|
|
438
|
+
void next.catch((error) => {
|
|
439
|
+
secretFailure = error instanceof Error ? error : new Error('signature-invalid');
|
|
440
|
+
}).finally(() => {
|
|
441
|
+
if (confirmationQueue === next)
|
|
442
|
+
confirmationQueue = undefined;
|
|
443
|
+
});
|
|
398
444
|
}
|
|
399
445
|
function applyConfirmedFrames(frames) {
|
|
400
446
|
for (const frame of frames) {
|
|
401
447
|
if (frame.tick > appliedThrough) {
|
|
402
|
-
confirmed.set(frame.tick, frame.inputs);
|
|
448
|
+
confirmed.set(frame.tick, { inputs: frame.inputs, commands: frame.commands ?? [] });
|
|
403
449
|
}
|
|
404
450
|
}
|
|
405
451
|
while (confirmed.has(appliedThrough + 1)) {
|
|
406
452
|
const tick = appliedThrough + 1;
|
|
407
|
-
const
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
453
|
+
const confFrame = confirmed.get(tick);
|
|
454
|
+
const confInputs = confFrame.inputs;
|
|
455
|
+
const predFrame = timeline[tick];
|
|
456
|
+
if (predFrame === undefined || (0, canonical_js_1.canonicalStringify)(confFrame) !== (0, canonical_js_1.canonicalStringify)(predFrame)) {
|
|
457
|
+
timeline[tick] = { inputs: confInputs.slice(), commands: confFrame.commands };
|
|
411
458
|
if (predictedThrough > tick) {
|
|
412
459
|
// Rollback: we had already predicted this tick and the confirmed inputs differ.
|
|
413
460
|
rollbacks += 1;
|
|
@@ -457,9 +504,9 @@ function createNetworkedSyncplayClient(options) {
|
|
|
457
504
|
/**
|
|
458
505
|
* Report the confirmed-state checksum at `tick`, read from the rolling
|
|
459
506
|
* snapshot buffer — NOT via restoreToFrame, which would rewind the live
|
|
460
|
-
* session out from under prediction.
|
|
461
|
-
*
|
|
462
|
-
*
|
|
507
|
+
* session out from under prediction. Quorum consistency comes from the wire
|
|
508
|
+
* checksum's full-state serialize/deserialize round-trip; synctest's codec
|
|
509
|
+
* phase verifies that round-trip is canonical on every retained frame.
|
|
463
510
|
* Ticks that fell out of the buffer are skipped (the client simply is not
|
|
464
511
|
* donor-eligible for them).
|
|
465
512
|
*/
|
|
@@ -469,7 +516,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
469
516
|
if (active === undefined || tick < 0 || role === 'spectator') {
|
|
470
517
|
return;
|
|
471
518
|
}
|
|
472
|
-
const snapshot = active.
|
|
519
|
+
const snapshot = active.peekSnapshot(tick + 1);
|
|
473
520
|
if (snapshot === undefined) {
|
|
474
521
|
return;
|
|
475
522
|
}
|
|
@@ -477,7 +524,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
477
524
|
kind: 'checksum-report',
|
|
478
525
|
slot,
|
|
479
526
|
tick,
|
|
480
|
-
checksum:
|
|
527
|
+
checksum: active.getWireStateChecksum(snapshot.frame),
|
|
481
528
|
}));
|
|
482
529
|
reportedTicks.push(tick);
|
|
483
530
|
if (reportedTicks.length > REPORTED_TICKS_RETAINED) {
|
|
@@ -531,8 +578,8 @@ function createNetworkedSyncplayClient(options) {
|
|
|
531
578
|
const minTick = minFrame - 1;
|
|
532
579
|
const candidates = reportedTicks.filter((tick) => tick >= minTick).sort((a, b) => a - b);
|
|
533
580
|
for (const tick of candidates) {
|
|
534
|
-
const snapshot = active.
|
|
535
|
-
const confirmedAtTick = timeline[tick];
|
|
581
|
+
const snapshot = active.peekSnapshot(tick + 1);
|
|
582
|
+
const confirmedAtTick = timeline[tick]?.inputs;
|
|
536
583
|
if (snapshot === undefined || confirmedAtTick === undefined) {
|
|
537
584
|
continue;
|
|
538
585
|
}
|
|
@@ -540,9 +587,9 @@ function createNetworkedSyncplayClient(options) {
|
|
|
540
587
|
v: 1,
|
|
541
588
|
tick,
|
|
542
589
|
sessionFrame: tick + 1,
|
|
543
|
-
stateBytes: (0, session_snapshot_js_1.bytesToBase64)(snapshot.
|
|
590
|
+
stateBytes: (0, session_snapshot_js_1.bytesToBase64)(active.getSnapshotBytes(snapshot.frame)),
|
|
544
591
|
randomState: snapshot.randomState,
|
|
545
|
-
stateChecksum:
|
|
592
|
+
stateChecksum: active.getWireStateChecksum(snapshot.frame),
|
|
546
593
|
confirmedInputs: confirmedAtTick.map((input) => (0, canonical_js_1.cloneCanonical)(input)),
|
|
547
594
|
});
|
|
548
595
|
donorTransferCounter += 1;
|
|
@@ -613,6 +660,8 @@ function createNetworkedSyncplayClient(options) {
|
|
|
613
660
|
randomState: snapshot.randomState,
|
|
614
661
|
inputs: snapshot.confirmedInputs.map((input) => decodeInputSafe(input)),
|
|
615
662
|
});
|
|
663
|
+
// The active state was just deserialized, so this is already the same
|
|
664
|
+
// round-tripped full-state basis used by the donor's wire checksum.
|
|
616
665
|
const actualChecksum = (0, canonical_js_1.defaultChecksum)(active.getState());
|
|
617
666
|
if (actualChecksum !== snapshot.stateChecksum) {
|
|
618
667
|
throw new errors_js_1.SyncplayError('Hydrated snapshot state does not match its declared checksum', {
|
|
@@ -627,7 +676,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
627
676
|
ownScheduleCursor = Math.max(ownScheduleCursor, snapshot.tick + 1);
|
|
628
677
|
lastConfirmedBySlot = snapshot.confirmedInputs.map((input) => (0, canonical_js_1.cloneCanonical)(input));
|
|
629
678
|
timeline.length = 0;
|
|
630
|
-
timeline[snapshot.tick] = lastConfirmedBySlot.slice();
|
|
679
|
+
timeline[snapshot.tick] = { inputs: lastConfirmedBySlot.slice(), commands: [] };
|
|
631
680
|
for (const key of [...ownInputs.keys()]) {
|
|
632
681
|
if (key <= snapshot.tick)
|
|
633
682
|
ownInputs.delete(key);
|
|
@@ -669,6 +718,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
669
718
|
maxRollbackDepth,
|
|
670
719
|
};
|
|
671
720
|
},
|
|
721
|
+
secrets,
|
|
672
722
|
whenReady() {
|
|
673
723
|
return readyPromise;
|
|
674
724
|
},
|
package/dist/cjs/node.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.stableEngineParityJson = exports.runEngineParitySuite = exports.explainEngineParityFixture = exports.runChaosCertificationSuite = exports.explainChaosFixture = exports.startLocalWebSocketProtocolServer = exports.runLocalWebSocketAdapterTest = exports.runNetworkedInputAuthoritySpike = exports.defaultNetworkedInputAuthoritySpikeOptions = exports.verifyReplayScoreAgainstTrustedRuntime = exports.verifyReplay = exports.createSyncplaySession = exports.createOfflineSession = void 0;
|
|
17
|
+
exports.cookConvexDecomposition = exports.assertCookedColliderIsRuntimeValid = exports.stableEngineParityJson = exports.runEngineParitySuite = exports.explainEngineParityFixture = exports.runChaosCertificationSuite = exports.explainChaosFixture = exports.startLocalWebSocketProtocolServer = exports.runLocalWebSocketAdapterTest = exports.runNetworkedInputAuthoritySpike = exports.defaultNetworkedInputAuthoritySpikeOptions = exports.verifySyncplaySignedReceipt = exports.verifyMerkleProof = exports.verifyHashChainReveal = exports.signedReceiptBytes = exports.sha256Bytes = exports.merkleProof = exports.merkleNodeHash = exports.merkleLeafHash = exports.hexToBytes = exports.createBrowserSyncplaySecretCrypto = exports.constantTimeEqual = exports.concatBytes = exports.bytesToHex = exports.buildMerkleTree = exports.verifyReplayScoreAgainstTrustedRuntime = exports.verifyReplay = exports.createSyncplaySession = exports.createOfflineSession = void 0;
|
|
18
18
|
__exportStar(require("./multiplayer-service.js"), exports);
|
|
19
19
|
__exportStar(require("./input-authority.js"), exports);
|
|
20
20
|
__exportStar(require("./local-authority-wire.js"), exports);
|
|
@@ -35,6 +35,24 @@ var replay_bundle_js_1 = require("./replay-bundle.js");
|
|
|
35
35
|
Object.defineProperty(exports, "verifyReplayScoreAgainstTrustedRuntime", { enumerable: true, get: function () { return replay_bundle_js_1.verifyReplayScoreAgainstTrustedRuntime; } });
|
|
36
36
|
__exportStar(require("./match-log.js"), exports);
|
|
37
37
|
__exportStar(require("./authority-room.js"), exports);
|
|
38
|
+
__exportStar(require("./secret-config.js"), exports);
|
|
39
|
+
__exportStar(require("./secret-protocol.js"), exports);
|
|
40
|
+
var secret_crypto_js_1 = require("./secret-crypto.js");
|
|
41
|
+
Object.defineProperty(exports, "buildMerkleTree", { enumerable: true, get: function () { return secret_crypto_js_1.buildMerkleTree; } });
|
|
42
|
+
Object.defineProperty(exports, "bytesToHex", { enumerable: true, get: function () { return secret_crypto_js_1.bytesToHex; } });
|
|
43
|
+
Object.defineProperty(exports, "concatBytes", { enumerable: true, get: function () { return secret_crypto_js_1.concatBytes; } });
|
|
44
|
+
Object.defineProperty(exports, "constantTimeEqual", { enumerable: true, get: function () { return secret_crypto_js_1.constantTimeEqual; } });
|
|
45
|
+
Object.defineProperty(exports, "createBrowserSyncplaySecretCrypto", { enumerable: true, get: function () { return secret_crypto_js_1.createBrowserSyncplaySecretCrypto; } });
|
|
46
|
+
Object.defineProperty(exports, "hexToBytes", { enumerable: true, get: function () { return secret_crypto_js_1.hexToBytes; } });
|
|
47
|
+
Object.defineProperty(exports, "merkleLeafHash", { enumerable: true, get: function () { return secret_crypto_js_1.merkleLeafHash; } });
|
|
48
|
+
Object.defineProperty(exports, "merkleNodeHash", { enumerable: true, get: function () { return secret_crypto_js_1.merkleNodeHash; } });
|
|
49
|
+
Object.defineProperty(exports, "merkleProof", { enumerable: true, get: function () { return secret_crypto_js_1.merkleProof; } });
|
|
50
|
+
Object.defineProperty(exports, "sha256Bytes", { enumerable: true, get: function () { return secret_crypto_js_1.sha256Bytes; } });
|
|
51
|
+
Object.defineProperty(exports, "signedReceiptBytes", { enumerable: true, get: function () { return secret_crypto_js_1.signedReceiptBytes; } });
|
|
52
|
+
Object.defineProperty(exports, "verifyHashChainReveal", { enumerable: true, get: function () { return secret_crypto_js_1.verifyHashChainReveal; } });
|
|
53
|
+
Object.defineProperty(exports, "verifyMerkleProof", { enumerable: true, get: function () { return secret_crypto_js_1.verifyMerkleProof; } });
|
|
54
|
+
Object.defineProperty(exports, "verifySyncplaySignedReceipt", { enumerable: true, get: function () { return secret_crypto_js_1.verifySyncplaySignedReceipt; } });
|
|
55
|
+
__exportStar(require("./secret-authority.js"), exports);
|
|
38
56
|
__exportStar(require("./ws-frame.js"), exports);
|
|
39
57
|
var networked_input_authority_js_1 = require("./networked-input-authority.js");
|
|
40
58
|
Object.defineProperty(exports, "defaultNetworkedInputAuthoritySpikeOptions", { enumerable: true, get: function () { return networked_input_authority_js_1.defaultNetworkedInputAuthoritySpikeOptions; } });
|
|
@@ -49,3 +67,7 @@ var engine_parity_js_1 = require("./engine-parity.js");
|
|
|
49
67
|
Object.defineProperty(exports, "explainEngineParityFixture", { enumerable: true, get: function () { return engine_parity_js_1.explainEngineParityFixture; } });
|
|
50
68
|
Object.defineProperty(exports, "runEngineParitySuite", { enumerable: true, get: function () { return engine_parity_js_1.runEngineParitySuite; } });
|
|
51
69
|
Object.defineProperty(exports, "stableEngineParityJson", { enumerable: true, get: function () { return engine_parity_js_1.stableEngineParityJson; } });
|
|
70
|
+
// Offline collider cooking — build-time only, never part of a simulation step.
|
|
71
|
+
var collider_cooking_js_1 = require("./collider-cooking.js");
|
|
72
|
+
Object.defineProperty(exports, "assertCookedColliderIsRuntimeValid", { enumerable: true, get: function () { return collider_cooking_js_1.assertCookedColliderIsRuntimeValid; } });
|
|
73
|
+
Object.defineProperty(exports, "cookConvexDecomposition", { enumerable: true, get: function () { return collider_cooking_js_1.cookConvexDecomposition; } });
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createOfflineSession = createOfflineSession;
|
|
4
4
|
const canonical_js_1 = require("./canonical.js");
|
|
5
|
+
const checksum_basis_js_1 = require("./checksum-basis.js");
|
|
5
6
|
const errors_js_1 = require("./errors.js");
|
|
6
7
|
const math_js_1 = require("./math.js");
|
|
7
8
|
const random_js_1 = require("./random.js");
|
|
@@ -14,6 +15,8 @@ function createOfflineSession(config) {
|
|
|
14
15
|
const randomSeed = config.randomSeed ?? 1;
|
|
15
16
|
const fixedFrameMs = 1000 / config.tickRate;
|
|
16
17
|
const math = (0, math_js_1.createDeterministicMath)();
|
|
18
|
+
// LOAD-BEARING: these two clones make frame-0 reference retention private to
|
|
19
|
+
// each session and insulate exportReplay's initialState.
|
|
17
20
|
const initialState = (0, canonical_js_1.cloneCanonical)(config.initialState);
|
|
18
21
|
const mutable = {
|
|
19
22
|
frame: 0,
|
|
@@ -165,20 +168,39 @@ function createOfflineSession(config) {
|
|
|
165
168
|
],
|
|
166
169
|
};
|
|
167
170
|
},
|
|
171
|
+
peekSnapshot(frame) {
|
|
172
|
+
return mutable.snapshots.get(frame);
|
|
173
|
+
},
|
|
168
174
|
getSnapshot(frame) {
|
|
169
175
|
const snapshot = mutable.snapshots.get(frame);
|
|
170
176
|
return snapshot === undefined ? undefined : cloneSnapshot(snapshot);
|
|
171
177
|
},
|
|
178
|
+
getSnapshotBytes(frame) {
|
|
179
|
+
return (0, checksum_basis_js_1.serializeStateWith)(config, requireSnapshot(mutable, frame).state);
|
|
180
|
+
},
|
|
181
|
+
exportExternalSnapshot(frame) {
|
|
182
|
+
const snapshot = requireSnapshot(mutable, frame);
|
|
183
|
+
return {
|
|
184
|
+
frame: snapshot.frame,
|
|
185
|
+
stateBytes: (0, checksum_basis_js_1.serializeStateWith)(config, snapshot.state),
|
|
186
|
+
randomState: snapshot.randomState,
|
|
187
|
+
inputs: snapshot.inputs.map((input) => cloneInput(config, input)),
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
getRecordedChecksum(frame) {
|
|
191
|
+
return requireSnapshot(mutable, frame).checksum;
|
|
192
|
+
},
|
|
193
|
+
computeRecordedChecksum(frame) {
|
|
194
|
+
const snapshot = requireSnapshot(mutable, frame);
|
|
195
|
+
return snapshot.checksum ?? getChecksum(config, snapshot.state);
|
|
196
|
+
},
|
|
197
|
+
getWireStateChecksum(frame) {
|
|
198
|
+
return (0, checksum_basis_js_1.computeWireStateChecksum)(config, requireSnapshot(mutable, frame).state);
|
|
199
|
+
},
|
|
172
200
|
restoreToFrame(frame) {
|
|
173
|
-
const snapshot = mutable
|
|
174
|
-
if (snapshot === undefined) {
|
|
175
|
-
throw new errors_js_1.SyncplayError(`No snapshot exists for frame ${frame}`, {
|
|
176
|
-
code: 'session.missing-snapshot',
|
|
177
|
-
frame,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
201
|
+
const snapshot = requireSnapshot(mutable, frame);
|
|
180
202
|
mutable.frame = snapshot.frame;
|
|
181
|
-
mutable.state =
|
|
203
|
+
mutable.state = snapshot.state;
|
|
182
204
|
mutable.inputs = snapshot.inputs.map((input) => cloneInput(config, input));
|
|
183
205
|
mutable.inputFlags = Array.from({ length: config.playerCount }, (_, slot) => defaultInputFlags(slot));
|
|
184
206
|
mutable.random.restore({ state: snapshot.randomState });
|
|
@@ -193,7 +215,7 @@ function createOfflineSession(config) {
|
|
|
193
215
|
(0, errors_js_1.invariant)(Number.isInteger(snapshot.frame) && snapshot.frame >= 0, 'External snapshot frame must be a non-negative integer', 'session.invalid-snapshot-frame');
|
|
194
216
|
(0, errors_js_1.invariant)(snapshot.inputs.length === config.playerCount, 'External snapshot inputs must cover every player slot', 'session.invalid-snapshot-inputs');
|
|
195
217
|
mutable.frame = snapshot.frame;
|
|
196
|
-
mutable.state =
|
|
218
|
+
mutable.state = (0, checksum_basis_js_1.deserializeStateWith)(config, snapshot.stateBytes);
|
|
197
219
|
mutable.inputs = snapshot.inputs.map((input) => cloneInput(config, input));
|
|
198
220
|
mutable.inputFlags = Array.from({ length: config.playerCount }, (_, slot) => defaultInputFlags(slot));
|
|
199
221
|
mutable.random.restore({ state: snapshot.randomState });
|
|
@@ -205,13 +227,16 @@ function createOfflineSession(config) {
|
|
|
205
227
|
mutable.snapshots = new Map();
|
|
206
228
|
mutable.snapshotOrder = [];
|
|
207
229
|
hydratedFromExternalSnapshot = true;
|
|
208
|
-
|
|
230
|
+
const hydratedChecksum = mutable.frame % checksumInterval === 0
|
|
231
|
+
? getChecksum(config, mutable.state)
|
|
232
|
+
: undefined;
|
|
233
|
+
recordSnapshot(config, mutable, snapshotBufferSize, hydratedChecksum);
|
|
209
234
|
},
|
|
210
235
|
getSnapshotFrames() {
|
|
211
236
|
return [...mutable.snapshotOrder];
|
|
212
237
|
},
|
|
213
238
|
};
|
|
214
|
-
recordSnapshot(config, mutable, snapshotBufferSize);
|
|
239
|
+
recordSnapshot(config, mutable, snapshotBufferSize, getChecksum(config, mutable.state));
|
|
215
240
|
return session;
|
|
216
241
|
}
|
|
217
242
|
function runFrames(config, mutable, checksumInterval, snapshotBufferSize, replayRecordingMode, math, count) {
|
|
@@ -251,8 +276,10 @@ function runFrames(config, mutable, checksumInterval, snapshotBufferSize, replay
|
|
|
251
276
|
? (0, runtime_guards_js_1.withRuntimeDeterminismGuards)(frame, () => config.step(mutable.state, effectiveInputs, frame, stepContext))
|
|
252
277
|
: config.step(mutable.state, effectiveInputs, frame, stepContext);
|
|
253
278
|
mutable.frame += 1;
|
|
254
|
-
const checksum =
|
|
255
|
-
|
|
279
|
+
const checksum = mutable.frame % checksumInterval === 0
|
|
280
|
+
? getChecksum(config, mutable.state)
|
|
281
|
+
: undefined;
|
|
282
|
+
if (checksum !== undefined) {
|
|
256
283
|
const checksumRecord = { frame: mutable.frame, checksum };
|
|
257
284
|
config.onChecksumRecorded?.(checksumRecord);
|
|
258
285
|
if (replayRecordingMode === 'memory') {
|
|
@@ -322,26 +349,28 @@ function pruneSnapshotsAfterFrame(mutable, frame) {
|
|
|
322
349
|
return true;
|
|
323
350
|
});
|
|
324
351
|
}
|
|
352
|
+
function requireSnapshot(mutable, frame) {
|
|
353
|
+
const snapshot = mutable.snapshots.get(frame);
|
|
354
|
+
if (snapshot === undefined) {
|
|
355
|
+
throw new errors_js_1.SyncplayError(`No snapshot exists for frame ${frame}`, {
|
|
356
|
+
code: 'session.missing-snapshot',
|
|
357
|
+
frame,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
return snapshot;
|
|
361
|
+
}
|
|
325
362
|
function getChecksum(config, state) {
|
|
326
363
|
return String(config.checksum?.(state) ?? (0, canonical_js_1.defaultChecksum)(config.canonicalizeState?.(state) ?? state));
|
|
327
364
|
}
|
|
328
365
|
function cloneInput(config, input) {
|
|
329
366
|
return (0, canonical_js_1.cloneCanonical)(input);
|
|
330
367
|
}
|
|
331
|
-
function serializeState(config, state) {
|
|
332
|
-
return config.serializeState?.(state) ?? (0, canonical_js_1.encodeCanonicalBytes)(config.canonicalizeState?.(state) ?? state);
|
|
333
|
-
}
|
|
334
|
-
function deserializeState(config, bytes) {
|
|
335
|
-
return config.deserializeState?.(bytes) ?? (0, canonical_js_1.decodeCanonicalBytes)(bytes);
|
|
336
|
-
}
|
|
337
368
|
function recordSnapshot(config, mutable, snapshotBufferSize, checksum) {
|
|
338
|
-
const stateBytes = serializeState(config, mutable.state);
|
|
339
369
|
const snapshot = {
|
|
340
370
|
frame: mutable.frame,
|
|
341
371
|
inputs: mutable.inputs.map((input) => cloneInput(config, input)),
|
|
342
|
-
checksum
|
|
343
|
-
state:
|
|
344
|
-
stateBytes,
|
|
372
|
+
checksum,
|
|
373
|
+
state: mutable.state,
|
|
345
374
|
randomState: mutable.random.snapshot().state,
|
|
346
375
|
};
|
|
347
376
|
const alreadyStored = mutable.snapshots.has(snapshot.frame);
|
|
@@ -364,7 +393,6 @@ function cloneSnapshot(snapshot) {
|
|
|
364
393
|
inputs: snapshot.inputs.map((input) => (0, canonical_js_1.cloneCanonical)(input)),
|
|
365
394
|
checksum: snapshot.checksum,
|
|
366
395
|
state: (0, canonical_js_1.cloneCanonical)(snapshot.state),
|
|
367
|
-
stateBytes: new Uint8Array(snapshot.stateBytes),
|
|
368
396
|
randomState: snapshot.randomState,
|
|
369
397
|
};
|
|
370
398
|
}
|
package/dist/cjs/physics-cert.js
CHANGED
|
@@ -62,7 +62,7 @@ function runPhysicsCert(options) {
|
|
|
62
62
|
snapshotHashStable;
|
|
63
63
|
return {
|
|
64
64
|
engine: options.engine,
|
|
65
|
-
engineVersion: options.engine === 'builtin-rigid' ? 'builtin-rigid@
|
|
65
|
+
engineVersion: options.engine === 'builtin-rigid' ? 'builtin-rigid@2' : 'builtin-fixed@1',
|
|
66
66
|
adapterVersion: 'physics-cert@1',
|
|
67
67
|
shape: options.shape,
|
|
68
68
|
players: options.players,
|
|
@@ -319,13 +319,18 @@ function buildRigidArena(options) {
|
|
|
319
319
|
// hinge door
|
|
320
320
|
bodies.push({ id: `frame-${slot}`, kind: 'static', x: baseX + 3, y: 2, z: 0, vx: 0, vy: 0, vz: 0, shape: { type: 'box', halfX: 0.1, halfY: 0.5, halfZ: 0.5 }, layer: 1, mask: 1 });
|
|
321
321
|
bodies.push({ id: `door-${slot}`, kind: 'dynamic', x: baseX + 4, y: 2, z: 0, vx: 0, vy: 0, vz: 0, mass: 1, orientation: ident, angularVel: { x: 0, y: 0, z: 0 }, shape: { type: 'box', halfX: 1, halfY: 0.5, halfZ: 0.1 }, layer: 1, mask: 1 });
|
|
322
|
-
joints.push({ id: `hinge-${slot}`, type: 'hinge', a: `frame-${slot}`, b: `door-${slot}`, anchorA: { x: 0, y: 0, z: 0 }, anchorB: { x: -1, y: 0, z: 0 }, axisA: { x: 0, y: 1, z: 0 }, motorSpeed: 0.02, maxMotorTorque: 50 });
|
|
322
|
+
joints.push({ id: `hinge-${slot}`, type: 'hinge', a: `frame-${slot}`, b: `door-${slot}`, anchorA: { x: 0, y: 0, z: 0 }, anchorB: { x: -1, y: 0, z: 0 }, axisA: { x: 0, y: 1, z: 0 }, motorSpeed: 0.02, maxMotorTorque: 50, disableCollision: true });
|
|
323
323
|
// grab-target box
|
|
324
324
|
bodies.push({ id: `grab-${slot}`, kind: 'dynamic', x: baseX + 6, y: 3, z: 0, vx: 0, vy: 0, vz: 0, mass: 1, orientation: ident, angularVel: { x: 0, y: 0, z: 0 }, shape: { type: 'box', halfX: 0.5, halfY: 0.5, halfZ: 0.5 }, layer: 1, mask: 1 });
|
|
325
325
|
joints.push({ id: `carry-${slot}`, type: 'grab', body: `grab-${slot}`, anchor: { x: 0, y: 0, z: 0 }, targetX: baseX + 6, targetY: 4, targetZ: 0, maxForce: 20 });
|
|
326
326
|
// ccd projectile toward a thin wall
|
|
327
327
|
bodies.push({ id: `wall-${slot}`, kind: 'static', x: baseX + 9, y: 2, z: 0, vx: 0, vy: 0, vz: 0, shape: { type: 'box', halfX: 0.05, halfY: 2, halfZ: 2 }, layer: 1, mask: 1 });
|
|
328
|
-
|
|
328
|
+
// vx 8 tunnels the 0.1-thick wall per step without CCD, so this exercises the
|
|
329
|
+
// exact-TOI ccd path while staying a settling-scale impact the convergence gate can
|
|
330
|
+
// reach. The extreme-speed case (vx 60 into a stack) is finiteness/determinism-tested
|
|
331
|
+
// in physics3d-ccd.test.ts — a physical punch-through does not re-settle, so it cannot
|
|
332
|
+
// live in a convergence budget.
|
|
333
|
+
bodies.push({ id: `bullet-${slot}`, kind: 'dynamic', x: baseX - 20, y: 2, z: 0, vx: 8, vy: 0, vz: 0, mass: 1, orientation: ident, angularVel: { x: 0, y: 0, z: 0 }, ccd: true, linearDamping: 1, angularDamping: 1, shape: { type: 'box', halfX: 0.2, halfY: 0.2, halfZ: 0.2 }, layer: 1, mask: 1 });
|
|
329
334
|
// soft spring: bob hung 2 below a static anchor, restLength 1 → oscillates and settles
|
|
330
335
|
bodies.push({ id: `spring-anchor-${slot}`, kind: 'static', x: baseX + 2, y: 6, z: 3, vx: 0, vy: 0, vz: 0, shape: { type: 'box', halfX: 0.1, halfY: 0.1, halfZ: 0.1 }, layer: 2, mask: 2 });
|
|
331
336
|
bodies.push({ id: `spring-bob-${slot}`, kind: 'dynamic', x: baseX + 2, y: 4, z: 3, vx: 0, vy: 0, vz: 0, mass: 1, orientation: ident, angularVel: { x: 0, y: 0, z: 0 }, shape: { type: 'box', halfX: 0.3, halfY: 0.3, halfZ: 0.3 }, layer: 2, mask: 2 });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.heightfieldLocalBounds = heightfieldLocalBounds;
|
|
4
|
+
exports.heightfieldLocalTriangles = heightfieldLocalTriangles;
|
|
5
|
+
function heightfieldLocalBounds(shape) {
|
|
6
|
+
let minHeight = shape.heights[0];
|
|
7
|
+
let maxHeight = shape.heights[0];
|
|
8
|
+
for (const height of shape.heights) {
|
|
9
|
+
minHeight = Math.min(minHeight, height);
|
|
10
|
+
maxHeight = Math.max(maxHeight, height);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
minX: 0,
|
|
14
|
+
maxX: (shape.columns - 1) * shape.scale.x,
|
|
15
|
+
minY: minHeight * shape.scale.y,
|
|
16
|
+
maxY: maxHeight * shape.scale.y,
|
|
17
|
+
minZ: 0,
|
|
18
|
+
maxZ: (shape.rows - 1) * shape.scale.z,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function vertex(shape, column, row) {
|
|
22
|
+
return {
|
|
23
|
+
x: column * shape.scale.x,
|
|
24
|
+
y: shape.heights[row * shape.columns + column] * shape.scale.y,
|
|
25
|
+
z: row * shape.scale.z,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function heightfieldLocalTriangles(shape, bounds) {
|
|
29
|
+
const minColumn = bounds === undefined ? 0 : Math.max(0, Math.floor(bounds.minX / shape.scale.x));
|
|
30
|
+
const maxColumn = bounds === undefined
|
|
31
|
+
? shape.columns - 2
|
|
32
|
+
: Math.min(shape.columns - 2, Math.ceil(bounds.maxX / shape.scale.x));
|
|
33
|
+
const minRow = bounds === undefined ? 0 : Math.max(0, Math.floor(bounds.minZ / shape.scale.z));
|
|
34
|
+
const maxRow = bounds === undefined
|
|
35
|
+
? shape.rows - 2
|
|
36
|
+
: Math.min(shape.rows - 2, Math.ceil(bounds.maxZ / shape.scale.z));
|
|
37
|
+
if (minColumn > maxColumn || minRow > maxRow) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const triangles = [];
|
|
41
|
+
for (let row = minRow; row <= maxRow; row += 1) {
|
|
42
|
+
for (let column = minColumn; column <= maxColumn; column += 1) {
|
|
43
|
+
const a = vertex(shape, column, row);
|
|
44
|
+
const b = vertex(shape, column, row + 1);
|
|
45
|
+
const c = vertex(shape, column + 1, row);
|
|
46
|
+
const d = vertex(shape, column + 1, row + 1);
|
|
47
|
+
triangles.push([a, b, c], [d, c, b]);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return triangles;
|
|
51
|
+
}
|