@series-inc/rundot-syncplay 5.23.0 → 5.24.0-beta.0
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 +20 -0
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +1 -0
- package/dist/checksum-basis.d.ts +4 -0
- package/dist/checksum-basis.js +17 -0
- package/dist/cjs/browser.js +4 -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-parity-matrix.js +32 -9
- package/dist/cjs/engine-parity.js +1 -1
- package/dist/cjs/index.js +8 -2
- package/dist/cjs/instant-replay.js +4 -9
- package/dist/cjs/movement3d.js +17 -1
- package/dist/cjs/networked-client.js +11 -9
- package/dist/cjs/node.js +5 -1
- package/dist/cjs/offline-session.js +52 -24
- package/dist/cjs/physics-cert.js +7 -2
- package/dist/cjs/physics3d-hull.js +381 -0
- package/dist/cjs/physics3d-joints.js +163 -31
- package/dist/cjs/physics3d-recipes.js +197 -0
- package/dist/cjs/physics3d-shared.js +6 -0
- package/dist/cjs/physics3d-solver.js +746 -89
- package/dist/cjs/physics3d-stacking.js +154 -0
- package/dist/cjs/physics3d-toi.js +251 -0
- package/dist/cjs/physics3d-vehicle.js +35 -10
- package/dist/cjs/physics3d.js +401 -20
- package/dist/cjs/runtime-support.js +71 -0
- package/dist/cjs/session-promotion.js +5 -5
- package/dist/cjs/synctest.js +49 -0
- package/dist/cjs/testing.js +4 -1
- package/dist/cli.js +163 -41
- package/dist/collider-cooking.d.ts +141 -0
- package/dist/collider-cooking.js +829 -0
- package/dist/deterministic-session.js +2 -0
- package/dist/engine-parity-matrix.js +32 -9
- package/dist/engine-parity.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/instant-replay.d.ts +2 -2
- package/dist/instant-replay.js +4 -9
- package/dist/movement3d.js +18 -2
- package/dist/networked-client.js +11 -9
- package/dist/node.d.ts +2 -0
- package/dist/node.js +2 -0
- package/dist/offline-session.js +53 -25
- package/dist/physics-cert.js +7 -2
- package/dist/physics3d-hull.d.ts +53 -0
- package/dist/physics3d-hull.js +376 -0
- package/dist/physics3d-joints.d.ts +7 -0
- package/dist/physics3d-joints.js +163 -31
- package/dist/physics3d-recipes.d.ts +29 -0
- package/dist/physics3d-recipes.js +193 -0
- package/dist/physics3d-shared.js +6 -0
- package/dist/physics3d-solver.d.ts +65 -1
- package/dist/physics3d-solver.js +744 -89
- package/dist/physics3d-stacking.d.ts +26 -0
- package/dist/physics3d-stacking.js +147 -0
- package/dist/physics3d-toi.d.ts +70 -0
- package/dist/physics3d-toi.js +245 -0
- package/dist/physics3d-vehicle.js +35 -10
- package/dist/physics3d.d.ts +20 -2
- package/dist/physics3d.js +400 -21
- package/dist/runtime-support.d.ts +60 -0
- package/dist/runtime-support.js +66 -0
- 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/types.d.ts +21 -2
- package/package.json +8 -3
|
@@ -7,11 +7,13 @@ exports.runDeterministicSessionCoreFixture = runDeterministicSessionCoreFixture;
|
|
|
7
7
|
exports.runDeterministicStructuredSessionInputFixture = runDeterministicStructuredSessionInputFixture;
|
|
8
8
|
exports.runDeterministicSessionAuthorityFixture = runDeterministicSessionAuthorityFixture;
|
|
9
9
|
exports.runDeterministicSessionLifecycleProtocolFixture = runDeterministicSessionLifecycleProtocolFixture;
|
|
10
|
+
const runtime_support_js_1 = require("./runtime-support.js");
|
|
10
11
|
const canonical_js_1 = require("./canonical.js");
|
|
11
12
|
const commands_js_1 = require("./commands.js");
|
|
12
13
|
const engine_runtime_js_1 = require("./engine-runtime.js");
|
|
13
14
|
const random_js_1 = require("./random.js");
|
|
14
15
|
function createDeterministicSessionCore(options) {
|
|
16
|
+
(0, runtime_support_js_1.assertSupportedJsRuntime)();
|
|
15
17
|
assertPositiveInteger(options.players, 'players');
|
|
16
18
|
if (options.reconnectGraceFrames !== undefined) {
|
|
17
19
|
assertNonNegativeInteger(options.reconnectGraceFrames, 'reconnectGraceFrames');
|
|
@@ -623,30 +623,47 @@ const engineParityMatrixEntries = [
|
|
|
623
623
|
'packages/syncplay/src/physics2d.ts',
|
|
624
624
|
'packages/syncplay/src/physics3d.ts',
|
|
625
625
|
'packages/syncplay/src/physics3d-solver.ts',
|
|
626
|
+
'packages/syncplay/src/physics3d-toi.ts',
|
|
626
627
|
'packages/syncplay/src/physics3d-joints.ts',
|
|
627
628
|
'packages/syncplay/tests/physics2d.test.ts',
|
|
628
629
|
'packages/syncplay/tests/physics3d-solver.test.ts',
|
|
629
630
|
'packages/syncplay/tests/physics3d-joints.test.ts',
|
|
630
631
|
'packages/syncplay/tests/physics3d-ccd.test.ts',
|
|
632
|
+
'packages/syncplay/tests/physics3d-toi.test.ts',
|
|
631
633
|
'packages/syncplay/tests/engine-complete.test.ts',
|
|
632
634
|
'packages/syncplay/src/physics3d-vehicle.ts',
|
|
633
635
|
'packages/syncplay/tests/physics3d-vehicle.test.ts',
|
|
634
|
-
'
|
|
636
|
+
'packages/syncplay/src/physics3d-stacking.ts',
|
|
637
|
+
'packages/syncplay/tests/physics3d-stacking.test.ts',
|
|
638
|
+
'packages/syncplay/src/physics3d-recipes.ts',
|
|
639
|
+
'packages/syncplay/tests/physics3d-recipes.test.ts',
|
|
640
|
+
'packages/syncplay/src/physics3d-hull.ts',
|
|
641
|
+
'packages/syncplay/tests/physics3d-hull.test.ts',
|
|
642
|
+
'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 (momentum transfers to the struck body — a fast bullet punches through and knocks targets loose, no non-physical stop-dead) with its position TOI-clamped so it never tunnels. A bullet at 60 units/tick into a resting stack or a frame+door cluster stays finite and bounded (fixes the #2821 relax divergence via witness-point contact anchors + a small speculative margin + shared multi-body seam impulse). Linear-sweep + stationary-target scope; glancing bodies slide (not frozen); non-ccd bodies still tunnel (opt-in). physics3d-ccd.test.ts covers momentum-transfer / no-freeze / finite-at-vx60 / rollback + permutation equivalence',
|
|
644
|
+
'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
|
+
'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
|
+
'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',
|
|
635
647
|
'builtin-rigid physics-cert lane passes all 4 determinism gates; contact/joint caches are checksummed rollback state',
|
|
636
|
-
'raycast-wheel vehicle controller (stepDeterministicVehicle3D): spring-damper suspension, friction-circle drive/steer/brake, fixed-point steering trig
|
|
637
|
-
'per-world static AABB-tree query index: raycast/overlap/shape-cast are O(log n + hits) with bit-identical results
|
|
638
|
-
'pooled narrow-phase/constraint scratch (scalarized box face-clip over ping-pong clip slots, step-scoped constraint pool)
|
|
648
|
+
'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
|
+
'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
|
+
'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
|
+
'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: a support sample projecting onto a triangle face uses its signed plane distance (capped at the sample shape\'s diameter), so a box or hull rests on and is pushed by a mesh instead of falling through it — previously only sphere/capsule (radius-bearing) samples could generate a resolvable mesh contact',
|
|
653
|
+
'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',
|
|
639
654
|
],
|
|
640
655
|
cliProofs: [
|
|
641
656
|
'npm run lab:physics3d -- --bodies 128 --frames 1000 --no-fast-stress --json artifacts/reports/physics3d-no-fast.json',
|
|
657
|
+
'npm run lab:physics3d-stacks -- --json artifacts/reports/physics3d-stacks.json',
|
|
642
658
|
'npm run certify:engine-complete -- --filter physics3d --players 128 --frames 5000 --no-fast-stress',
|
|
643
659
|
'npm run lab:physics-cert -- --engine builtin-rigid --shape fixed-arena --players 4 --frames 600 --seed 7 --snapshot-every 8',
|
|
644
660
|
'npm run coverage:package:physics-vehicle',
|
|
661
|
+
'npm run collider:validate -- <mesh.json> --json artifacts/reports/collider-validate.json',
|
|
645
662
|
],
|
|
646
663
|
gaps: [
|
|
647
|
-
'
|
|
648
|
-
'
|
|
649
|
-
'
|
|
664
|
+
'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
|
+
'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).',
|
|
666
|
+
'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.',
|
|
650
667
|
],
|
|
651
668
|
nextCliWork: [
|
|
652
669
|
'Profile the remaining burst cost before further pooling; no known single lever.',
|
|
@@ -829,20 +846,26 @@ const engineParityMatrixEntries = [
|
|
|
829
846
|
canCloseWithCli: true,
|
|
830
847
|
currentEvidence: [
|
|
831
848
|
'packages/syncplay/src/browser.ts',
|
|
849
|
+
'packages/syncplay/src/runtime-support.ts',
|
|
850
|
+
'packages/syncplay/tests/runtime-support.test.ts',
|
|
832
851
|
'packages/sdk/src/syncplay/index.ts',
|
|
833
852
|
'packages/sdk/src/syncplay/browser.ts',
|
|
834
853
|
'packages/sdk/src/syncplay/node.ts',
|
|
835
854
|
'packages/sdk/tests/deterministicMultiplayer.test.ts',
|
|
855
|
+
'cross-VM certification: Chromium (V8) AND WebKit (JavaScriptCore, the executable proxy for iOS WKWebView) are blocking gates on every engine PR (.github/workflows/ci-syncplay.yml cross-vm-browser). Each runs the replay proof, the number lab, and the 128-player/1000-frame engine-core numeric proof, and each must reproduce Node\'s checksums bit-for-bit; static:determinism requires a passing proof artifact per certified runtime, not just chromium',
|
|
856
|
+
'Hermes is an explicit non-goal with an enforced runtime guard: H5 games run in the platform WebView (V8 on Android, JavaScriptCore on iOS), never in React Native\'s VM, and no lane can execute Hermes — so createDeterministicSessionCore refuses to start there rather than emit checksums the authority server would silently reject (src/runtime-support.ts)',
|
|
836
857
|
],
|
|
837
858
|
cliProofs: [
|
|
838
859
|
'npm run lab:replay-verify-browser -- artifacts/replays/browser-proof-current.json --runtime chromium --json artifacts/certification/browser-replay-chromium-current.json',
|
|
860
|
+
'npm run lab:replay-verify-browser -- artifacts/replays/browser-proof-current.json --runtime webkit --json artifacts/certification/browser-replay-webkit-current.json',
|
|
861
|
+
'npm run lab:engine-core-browser -- --runtime webkit --json artifacts/certification/engine-core-numeric-webkit-current.json',
|
|
839
862
|
'cd packages/sdk && npm test -- deterministicMultiplayer.test.ts --runInBand && npm run build:sdk',
|
|
840
863
|
],
|
|
841
864
|
gaps: [
|
|
842
|
-
'
|
|
865
|
+
'Android WebView is proven only by proxy (Chromium/V8 on Linux); the on-device APK replay harness stays a manual per-release step.',
|
|
843
866
|
],
|
|
844
867
|
nextCliWork: [
|
|
845
|
-
'
|
|
868
|
+
'Automate the Android WebView APK replay harness so the on-device proof stops being manual.',
|
|
846
869
|
],
|
|
847
870
|
referenceDocs: [
|
|
848
871
|
'external-deterministic-engine-reference:manual/replay',
|
|
@@ -2531,7 +2531,7 @@ function runReplayWorkload128(frames, seed) {
|
|
|
2531
2531
|
mismatches: verification.mismatches,
|
|
2532
2532
|
finalChecksum: verification.finalChecksum,
|
|
2533
2533
|
replayBytes: JSON.stringify(replay).length,
|
|
2534
|
-
snapshotBytes: session.
|
|
2534
|
+
snapshotBytes: session.getSnapshotBytes(session.currentFrame).byteLength,
|
|
2535
2535
|
};
|
|
2536
2536
|
}
|
|
2537
2537
|
function runSecurityHostileClientCategory(config) {
|
package/dist/cjs/index.js
CHANGED
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.createDeterministicCommandTimelineSnapshot = exports.createDeterministicCommandTimeline = exports.assertDeterministicPayloadFields = exports.translateAabb = exports.sortAabbsByStableId = exports.segmentsIntersect = exports.segmentSegmentDistance = exports.segmentPointDistance = exports.pointOnSegment = exports.orientation = exports.fixedVec2 = exports.fixedCircle = exports.fixedAabb = exports.closestPointOnSegment = exports.circleIntersectsAabb = exports.circleIntersects = exports.aabbIntersects = exports.aabbContainsPoint = exports.stableSortedKeys = exports.stableEntries = exports.deterministicShuffle = exports.createStableIndexedMap = exports.encodeUtf8 = exports.defaultChecksum = exports.decodeUtf8 = exports.decodeCanonicalValue = exports.cloneCanonical = exports.canonicalStringify = exports.canonicalize = exports.tickDeterministicHfsm = exports.tickDeterministicBehaviorTreeWithMemory = exports.tickDeterministicBehaviorTree = exports.selectDeterministicUtility = exports.runDeterministicBotSimulation = exports.getBlackboardValue = exports.createDeterministicBotBlackboard = exports.cookDeterministicBotAsset = exports.traceDeterministicBotDocument = exports.summarizeDeterministicBotDocumentTraceCoverage = exports.summarizeDeterministicBotDocumentTraceCounterDelta = exports.compileDeterministicBotDocument = exports.createDeterministicAuthorityRoom = exports.stableAssetCookJson = exports.materializePrototypeEntities = exports.cookDeterministicAssets = exports.assertAssetManifestMatches = exports.stepDeterministicAnimation = exports.reconstructDeterministicAnimationEvents = exports.createDeterministicAnimationRuntimeState = exports.cookDeterministicAnimation = void 0;
|
|
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
|
-
exports.
|
|
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 = void 0;
|
|
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;
|
|
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; } });
|
|
@@ -207,6 +207,12 @@ Object.defineProperty(exports, "setDeterministicBodyVelocity3D", { enumerable: t
|
|
|
207
207
|
Object.defineProperty(exports, "shapeCastDeterministicPhysics3D", { enumerable: true, get: function () { return physics3d_js_1.shapeCastDeterministicPhysics3D; } });
|
|
208
208
|
Object.defineProperty(exports, "shapeCastDeterministicPhysics3DDetailed", { enumerable: true, get: function () { return physics3d_js_1.shapeCastDeterministicPhysics3DDetailed; } });
|
|
209
209
|
Object.defineProperty(exports, "stepDeterministicPhysicsWorld3D", { enumerable: true, get: function () { return physics3d_js_1.stepDeterministicPhysicsWorld3D; } });
|
|
210
|
+
var physics3d_stacking_js_1 = require("./physics3d-stacking.js");
|
|
211
|
+
Object.defineProperty(exports, "runDeterministicKickRecovery3DScenario", { enumerable: true, get: function () { return physics3d_stacking_js_1.runDeterministicKickRecovery3DScenario; } });
|
|
212
|
+
Object.defineProperty(exports, "runDeterministicMassRatio3DScenario", { enumerable: true, get: function () { return physics3d_stacking_js_1.runDeterministicMassRatio3DScenario; } });
|
|
213
|
+
Object.defineProperty(exports, "runDeterministicPyramid3DScenario", { enumerable: true, get: function () { return physics3d_stacking_js_1.runDeterministicPyramid3DScenario; } });
|
|
214
|
+
Object.defineProperty(exports, "runDeterministicStackingSuite3D", { enumerable: true, get: function () { return physics3d_stacking_js_1.runDeterministicStackingSuite3D; } });
|
|
215
|
+
Object.defineProperty(exports, "runDeterministicTower3DScenario", { enumerable: true, get: function () { return physics3d_stacking_js_1.runDeterministicTower3DScenario; } });
|
|
210
216
|
var physics3d_vehicle_js_1 = require("./physics3d-vehicle.js");
|
|
211
217
|
Object.defineProperty(exports, "stepDeterministicVehicle3D", { enumerable: true, get: function () { return physics3d_vehicle_js_1.stepDeterministicVehicle3D; } });
|
|
212
218
|
var r3f_render_adapter_js_1 = require("./r3f-render-adapter.js");
|
|
@@ -22,15 +22,10 @@ function runInstantReplay(options) {
|
|
|
22
22
|
options.onFrame(session.getState(), session.currentFrame);
|
|
23
23
|
expectedFrame += 1;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
code: 'instant-replay.missing-final-snapshot',
|
|
30
|
-
frame: session.currentFrame,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
return { framesPlayed: options.inputs.length, finalChecksum: finalSnapshot.checksum };
|
|
25
|
+
return {
|
|
26
|
+
framesPlayed: options.inputs.length,
|
|
27
|
+
finalChecksum: session.computeRecordedChecksum(session.currentFrame),
|
|
28
|
+
};
|
|
34
29
|
}
|
|
35
30
|
/**
|
|
36
31
|
* Binds a session config to a replay window. Each `run` simulates on a fresh
|
package/dist/cjs/movement3d.js
CHANGED
|
@@ -188,8 +188,11 @@ function computeKccPushImpulses(physics, x, y, z, velocity, geometry, maxPushImp
|
|
|
188
188
|
const magnitude = Math.min(speedIntoBody * KCC_PUSH_MASS, maxPushImpulse);
|
|
189
189
|
impulses.push({
|
|
190
190
|
bodyId: body.id,
|
|
191
|
+
// Apply through the body's center of mass so the horizontal push slides the
|
|
192
|
+
// body rather than torquing it — the soft-step solver's livelier angular
|
|
193
|
+
// response otherwise lets an above-COM push tumble a light crate.
|
|
191
194
|
impulse: { x: dirX * magnitude, y: 0, z: dirZ * magnitude },
|
|
192
|
-
point: { x: body.x, y, z: body.z },
|
|
195
|
+
point: { x: body.x, y: body.y, z: body.z },
|
|
193
196
|
});
|
|
194
197
|
}
|
|
195
198
|
return impulses;
|
|
@@ -260,6 +263,19 @@ function primitiveHalfExtent(shape, m) {
|
|
|
260
263
|
if (shape.type === 'sphere') {
|
|
261
264
|
return { x: shape.radius, y: shape.radius, z: shape.radius };
|
|
262
265
|
}
|
|
266
|
+
if (shape.type === 'hull') {
|
|
267
|
+
// Same symmetric bound the solver uses (physics3d-solver shapeHalfExtent).
|
|
268
|
+
let ext = { x: 0, y: 0, z: 0 };
|
|
269
|
+
for (const vertex of shape.vertices) {
|
|
270
|
+
const rotated = (0, physics3d_shared_js_1.mulMat3Vec)(m, vertex);
|
|
271
|
+
ext = {
|
|
272
|
+
x: Math.max(ext.x, Math.abs(rotated.x)),
|
|
273
|
+
y: Math.max(ext.y, Math.abs(rotated.y)),
|
|
274
|
+
z: Math.max(ext.z, Math.abs(rotated.z)),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
return ext;
|
|
278
|
+
}
|
|
263
279
|
if (shape.type === 'box') {
|
|
264
280
|
return {
|
|
265
281
|
x: Math.abs(m[0]) * shape.halfX + Math.abs(m[1]) * shape.halfY + Math.abs(m[2]) * shape.halfZ,
|
|
@@ -394,7 +394,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
394
394
|
return '';
|
|
395
395
|
}
|
|
396
396
|
active.restoreToFrame(frame);
|
|
397
|
-
return
|
|
397
|
+
return active.getWireStateChecksum(frame);
|
|
398
398
|
}
|
|
399
399
|
function applyConfirmedFrames(frames) {
|
|
400
400
|
for (const frame of frames) {
|
|
@@ -457,9 +457,9 @@ function createNetworkedSyncplayClient(options) {
|
|
|
457
457
|
/**
|
|
458
458
|
* Report the confirmed-state checksum at `tick`, read from the rolling
|
|
459
459
|
* snapshot buffer — NOT via restoreToFrame, which would rewind the live
|
|
460
|
-
* session out from under prediction.
|
|
461
|
-
*
|
|
462
|
-
*
|
|
460
|
+
* session out from under prediction. Quorum consistency comes from the wire
|
|
461
|
+
* checksum's full-state serialize/deserialize round-trip; synctest's codec
|
|
462
|
+
* phase verifies that round-trip is canonical on every retained frame.
|
|
463
463
|
* Ticks that fell out of the buffer are skipped (the client simply is not
|
|
464
464
|
* donor-eligible for them).
|
|
465
465
|
*/
|
|
@@ -469,7 +469,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
469
469
|
if (active === undefined || tick < 0 || role === 'spectator') {
|
|
470
470
|
return;
|
|
471
471
|
}
|
|
472
|
-
const snapshot = active.
|
|
472
|
+
const snapshot = active.peekSnapshot(tick + 1);
|
|
473
473
|
if (snapshot === undefined) {
|
|
474
474
|
return;
|
|
475
475
|
}
|
|
@@ -477,7 +477,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
477
477
|
kind: 'checksum-report',
|
|
478
478
|
slot,
|
|
479
479
|
tick,
|
|
480
|
-
checksum:
|
|
480
|
+
checksum: active.getWireStateChecksum(snapshot.frame),
|
|
481
481
|
}));
|
|
482
482
|
reportedTicks.push(tick);
|
|
483
483
|
if (reportedTicks.length > REPORTED_TICKS_RETAINED) {
|
|
@@ -531,7 +531,7 @@ function createNetworkedSyncplayClient(options) {
|
|
|
531
531
|
const minTick = minFrame - 1;
|
|
532
532
|
const candidates = reportedTicks.filter((tick) => tick >= minTick).sort((a, b) => a - b);
|
|
533
533
|
for (const tick of candidates) {
|
|
534
|
-
const snapshot = active.
|
|
534
|
+
const snapshot = active.peekSnapshot(tick + 1);
|
|
535
535
|
const confirmedAtTick = timeline[tick];
|
|
536
536
|
if (snapshot === undefined || confirmedAtTick === undefined) {
|
|
537
537
|
continue;
|
|
@@ -540,9 +540,9 @@ function createNetworkedSyncplayClient(options) {
|
|
|
540
540
|
v: 1,
|
|
541
541
|
tick,
|
|
542
542
|
sessionFrame: tick + 1,
|
|
543
|
-
stateBytes: (0, session_snapshot_js_1.bytesToBase64)(snapshot.
|
|
543
|
+
stateBytes: (0, session_snapshot_js_1.bytesToBase64)(active.getSnapshotBytes(snapshot.frame)),
|
|
544
544
|
randomState: snapshot.randomState,
|
|
545
|
-
stateChecksum:
|
|
545
|
+
stateChecksum: active.getWireStateChecksum(snapshot.frame),
|
|
546
546
|
confirmedInputs: confirmedAtTick.map((input) => (0, canonical_js_1.cloneCanonical)(input)),
|
|
547
547
|
});
|
|
548
548
|
donorTransferCounter += 1;
|
|
@@ -613,6 +613,8 @@ function createNetworkedSyncplayClient(options) {
|
|
|
613
613
|
randomState: snapshot.randomState,
|
|
614
614
|
inputs: snapshot.confirmedInputs.map((input) => decodeInputSafe(input)),
|
|
615
615
|
});
|
|
616
|
+
// The active state was just deserialized, so this is already the same
|
|
617
|
+
// round-tripped full-state basis used by the donor's wire checksum.
|
|
616
618
|
const actualChecksum = (0, canonical_js_1.defaultChecksum)(active.getState());
|
|
617
619
|
if (actualChecksum !== snapshot.stateChecksum) {
|
|
618
620
|
throw new errors_js_1.SyncplayError('Hydrated snapshot state does not match its declared checksum', {
|
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.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);
|
|
@@ -49,3 +49,7 @@ var engine_parity_js_1 = require("./engine-parity.js");
|
|
|
49
49
|
Object.defineProperty(exports, "explainEngineParityFixture", { enumerable: true, get: function () { return engine_parity_js_1.explainEngineParityFixture; } });
|
|
50
50
|
Object.defineProperty(exports, "runEngineParitySuite", { enumerable: true, get: function () { return engine_parity_js_1.runEngineParitySuite; } });
|
|
51
51
|
Object.defineProperty(exports, "stableEngineParityJson", { enumerable: true, get: function () { return engine_parity_js_1.stableEngineParityJson; } });
|
|
52
|
+
// Offline collider cooking — build-time only, never part of a simulation step.
|
|
53
|
+
var collider_cooking_js_1 = require("./collider-cooking.js");
|
|
54
|
+
Object.defineProperty(exports, "assertCookedColliderIsRuntimeValid", { enumerable: true, get: function () { return collider_cooking_js_1.assertCookedColliderIsRuntimeValid; } });
|
|
55
|
+
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
|
@@ -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 });
|