@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.
Files changed (131) hide show
  1. package/README.md +53 -12
  2. package/dist/authority-room.d.ts +11 -0
  3. package/dist/authority-room.js +43 -0
  4. package/dist/browser.d.ts +11 -1
  5. package/dist/browser.js +5 -0
  6. package/dist/checksum-basis.d.ts +4 -0
  7. package/dist/checksum-basis.js +17 -0
  8. package/dist/cjs/authority-room.js +43 -0
  9. package/dist/cjs/browser.js +18 -2
  10. package/dist/cjs/checksum-basis.js +22 -0
  11. package/dist/cjs/collider-cooking.js +834 -0
  12. package/dist/cjs/deterministic-session.js +2 -0
  13. package/dist/cjs/engine-complete.js +1 -4
  14. package/dist/cjs/engine-parity-matrix.js +41 -9
  15. package/dist/cjs/engine-parity.js +2 -2
  16. package/dist/cjs/index.js +24 -2
  17. package/dist/cjs/input-authority.js +34 -2
  18. package/dist/cjs/instant-replay.js +4 -9
  19. package/dist/cjs/local-authority-server-runner.js +3 -1
  20. package/dist/cjs/local-authority-wire.js +20 -1
  21. package/dist/cjs/match-log.js +27 -0
  22. package/dist/cjs/movement3d.js +17 -1
  23. package/dist/cjs/networked-client.js +70 -20
  24. package/dist/cjs/node.js +23 -1
  25. package/dist/cjs/offline-session.js +52 -24
  26. package/dist/cjs/physics-cert.js +8 -3
  27. package/dist/cjs/physics3d-heightfield.js +51 -0
  28. package/dist/cjs/physics3d-hull.js +381 -0
  29. package/dist/cjs/physics3d-joints.js +255 -49
  30. package/dist/cjs/physics3d-mass.js +131 -0
  31. package/dist/cjs/physics3d-recipes.js +197 -0
  32. package/dist/cjs/physics3d-shared.js +11 -1
  33. package/dist/cjs/physics3d-solver.js +845 -98
  34. package/dist/cjs/physics3d-stacking.js +154 -0
  35. package/dist/cjs/physics3d-toi.js +274 -0
  36. package/dist/cjs/physics3d-vehicle.js +35 -10
  37. package/dist/cjs/physics3d.js +601 -38
  38. package/dist/cjs/replay.js +25 -1
  39. package/dist/cjs/runtime-support.js +71 -0
  40. package/dist/cjs/sdk-offline.js +9 -1
  41. package/dist/cjs/sdk-session.js +8 -0
  42. package/dist/cjs/secret-authority.js +647 -0
  43. package/dist/cjs/secret-client.js +615 -0
  44. package/dist/cjs/secret-command-classification.js +8 -0
  45. package/dist/cjs/secret-config.js +211 -0
  46. package/dist/cjs/secret-crypto.js +208 -0
  47. package/dist/cjs/secret-protocol.js +238 -0
  48. package/dist/cjs/session-build.js +7 -4
  49. package/dist/cjs/session-promotion.js +5 -5
  50. package/dist/cjs/synctest.js +49 -0
  51. package/dist/cjs/testing.js +4 -1
  52. package/dist/cjs/tools/static-checker.js +61 -13
  53. package/dist/cli.js +193 -48
  54. package/dist/collider-cooking.d.ts +141 -0
  55. package/dist/collider-cooking.js +829 -0
  56. package/dist/deterministic-primitives.d.ts +1 -0
  57. package/dist/deterministic-session.js +2 -0
  58. package/dist/engine-complete.js +1 -4
  59. package/dist/engine-parity-matrix.js +41 -9
  60. package/dist/engine-parity.js +2 -2
  61. package/dist/index.d.ts +12 -1
  62. package/dist/index.js +6 -0
  63. package/dist/input-authority.d.ts +3 -0
  64. package/dist/input-authority.js +35 -3
  65. package/dist/instant-replay.d.ts +2 -2
  66. package/dist/instant-replay.js +4 -9
  67. package/dist/local-authority-server-runner.d.ts +1 -0
  68. package/dist/local-authority-server-runner.js +2 -1
  69. package/dist/local-authority-wire.js +20 -1
  70. package/dist/match-log.d.ts +4 -1
  71. package/dist/match-log.js +27 -0
  72. package/dist/movement3d.js +18 -2
  73. package/dist/networked-client.d.ts +8 -0
  74. package/dist/networked-client.js +70 -20
  75. package/dist/node.d.ts +7 -0
  76. package/dist/node.js +6 -0
  77. package/dist/offline-session.js +53 -25
  78. package/dist/physics-cert.js +8 -3
  79. package/dist/physics3d-heightfield.d.ts +17 -0
  80. package/dist/physics3d-heightfield.js +47 -0
  81. package/dist/physics3d-hull.d.ts +53 -0
  82. package/dist/physics3d-hull.js +376 -0
  83. package/dist/physics3d-joints.d.ts +13 -1
  84. package/dist/physics3d-joints.js +256 -50
  85. package/dist/physics3d-mass.d.ts +11 -0
  86. package/dist/physics3d-mass.js +126 -0
  87. package/dist/physics3d-recipes.d.ts +29 -0
  88. package/dist/physics3d-recipes.js +193 -0
  89. package/dist/physics3d-shared.d.ts +1 -0
  90. package/dist/physics3d-shared.js +10 -1
  91. package/dist/physics3d-solver.d.ts +78 -2
  92. package/dist/physics3d-solver.js +842 -98
  93. package/dist/physics3d-stacking.d.ts +26 -0
  94. package/dist/physics3d-stacking.js +147 -0
  95. package/dist/physics3d-toi.d.ts +79 -0
  96. package/dist/physics3d-toi.js +267 -0
  97. package/dist/physics3d-vehicle.js +35 -10
  98. package/dist/physics3d.d.ts +28 -3
  99. package/dist/physics3d.js +601 -40
  100. package/dist/replay.d.ts +5 -0
  101. package/dist/replay.js +25 -1
  102. package/dist/reports.d.ts +6 -1
  103. package/dist/runtime-support.d.ts +60 -0
  104. package/dist/runtime-support.js +66 -0
  105. package/dist/sdk-offline.d.ts +4 -0
  106. package/dist/sdk-offline.js +9 -1
  107. package/dist/sdk-session.d.ts +4 -0
  108. package/dist/sdk-session.js +8 -0
  109. package/dist/secret-authority.d.ts +90 -0
  110. package/dist/secret-authority.js +643 -0
  111. package/dist/secret-client.d.ts +89 -0
  112. package/dist/secret-client.js +611 -0
  113. package/dist/secret-command-classification.d.ts +1 -0
  114. package/dist/secret-command-classification.js +5 -0
  115. package/dist/secret-config.d.ts +45 -0
  116. package/dist/secret-config.js +206 -0
  117. package/dist/secret-crypto.d.ts +29 -0
  118. package/dist/secret-crypto.js +190 -0
  119. package/dist/secret-protocol.d.ts +181 -0
  120. package/dist/secret-protocol.js +230 -0
  121. package/dist/session-build.d.ts +16 -0
  122. package/dist/session-build.js +7 -4
  123. package/dist/session-promotion.js +5 -5
  124. package/dist/synctest.d.ts +1 -1
  125. package/dist/synctest.js +49 -0
  126. package/dist/testing.d.ts +2 -0
  127. package/dist/testing.js +1 -0
  128. package/dist/tools/static-checker.d.ts +11 -0
  129. package/dist/tools/static-checker.js +60 -13
  130. package/dist/types.d.ts +21 -2
  131. 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');
@@ -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
- // Rigid-solver material response (impulse-based) — combine semantics unchanged
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
@@ -623,30 +623,56 @@ 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',
628
+ 'packages/syncplay/src/physics3d-mass.ts',
629
+ 'packages/syncplay/src/physics3d-heightfield.ts',
627
630
  'packages/syncplay/tests/physics2d.test.ts',
628
631
  'packages/syncplay/tests/physics3d-solver.test.ts',
629
632
  'packages/syncplay/tests/physics3d-joints.test.ts',
630
633
  'packages/syncplay/tests/physics3d-ccd.test.ts',
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',
631
640
  'packages/syncplay/tests/engine-complete.test.ts',
632
641
  'packages/syncplay/src/physics3d-vehicle.ts',
633
642
  'packages/syncplay/tests/physics3d-vehicle.test.ts',
634
- 'warm-started sequential-impulse solver: stable stacking, tumbling, sleeping, axis locks, joints (ball/hinge/weld/rope/grab), speculative-contact CCD, compound colliders, orientation-aware queries',
643
+ 'packages/syncplay/src/physics3d-stacking.ts',
644
+ 'packages/syncplay/tests/physics3d-stacking.test.ts',
645
+ 'packages/syncplay/src/physics3d-recipes.ts',
646
+ 'packages/syncplay/tests/physics3d-recipes.test.ts',
647
+ 'packages/syncplay/src/physics3d-hull.ts',
648
+ 'packages/syncplay/tests/physics3d-hull.test.ts',
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',
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',
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',
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',
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',
635
654
  '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; certified via the physics-rigid-vehicle fixture, the builtin-rigid arena car, and a rollback-every-frame synctest scene',
637
- 'per-world static AABB-tree query index: raycast/overlap/shape-cast are O(log n + hits) with bit-identical results (oracle-tested; cert-lane checksums unchanged); batched applyDeterministicImpulses3D applies a frame of controller impulses in one canonical rebuild',
638
- 'pooled narrow-phase/constraint scratch (scalarized box face-clip over ping-pong clip slots, step-scoped constraint pool) pulled the 128-body pile impact-burst p99 from 2.06 ms to 1.54 ms (p50 0.85 0.72 ms, median-of-5); prismatic/breakable/collision-suppressed joints, a soft frequencyHz/dampingRatio spring joint, and per-body gravityScale round out the joint/body surface joint breaks are sticky checksummed cache 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',
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)',
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',
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',
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',
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',
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',
639
662
  ],
640
663
  cliProofs: [
641
664
  'npm run lab:physics3d -- --bodies 128 --frames 1000 --no-fast-stress --json artifacts/reports/physics3d-no-fast.json',
665
+ 'npm run lab:physics3d-stacks -- --json artifacts/reports/physics3d-stacks.json',
642
666
  'npm run certify:engine-complete -- --filter physics3d --players 128 --frames 5000 --no-fast-stress',
643
667
  'npm run lab:physics-cert -- --engine builtin-rigid --shape fixed-arena --players 4 --frames 600 --seed 7 --snapshot-every 8',
644
668
  'npm run coverage:package:physics-vehicle',
669
+ 'npm run collider:validate -- <mesh.json> --json artifacts/reports/collider-validate.json',
645
670
  ],
646
671
  gaps: [
647
- 'Vehicles are raycast-wheel only (static ground, flat/axis-aligned contact normals); moving-ground reaction and rigid-wheel vehicles are tracked follow-ons.',
648
- 'Dynamic mesh-vs-mesh collision, convex-hull decomposition / visual collider import, and ragdolls-in-checksummed-state remain out of scope.',
649
- 'Cross-VM certification posture unchanged (V8↔V8 certified; WebKit nightly; Hermes documented gap).',
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).',
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.',
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.',
650
676
  ],
651
677
  nextCliWork: [
652
678
  'Profile the remaining burst cost before further pooling; no known single lever.',
@@ -829,20 +855,26 @@ const engineParityMatrixEntries = [
829
855
  canCloseWithCli: true,
830
856
  currentEvidence: [
831
857
  'packages/syncplay/src/browser.ts',
858
+ 'packages/syncplay/src/runtime-support.ts',
859
+ 'packages/syncplay/tests/runtime-support.test.ts',
832
860
  'packages/sdk/src/syncplay/index.ts',
833
861
  'packages/sdk/src/syncplay/browser.ts',
834
862
  'packages/sdk/src/syncplay/node.ts',
835
863
  'packages/sdk/tests/deterministicMultiplayer.test.ts',
864
+ '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',
865
+ '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
866
  ],
837
867
  cliProofs: [
838
868
  'npm run lab:replay-verify-browser -- artifacts/replays/browser-proof-current.json --runtime chromium --json artifacts/certification/browser-replay-chromium-current.json',
869
+ 'npm run lab:replay-verify-browser -- artifacts/replays/browser-proof-current.json --runtime webkit --json artifacts/certification/browser-replay-webkit-current.json',
870
+ 'npm run lab:engine-core-browser -- --runtime webkit --json artifacts/certification/engine-core-numeric-webkit-current.json',
839
871
  'cd packages/sdk && npm test -- deterministicMultiplayer.test.ts --runInBand && npm run build:sdk',
840
872
  ],
841
873
  gaps: [
842
- 'Browser proof artifacts are not always present and are not yet mandatory for every parity report.',
874
+ 'Android WebView is proven only by proxy (Chromium/V8 on Linux); the on-device APK replay harness stays a manual per-release step.',
843
875
  ],
844
876
  nextCliWork: [
845
- 'Require Chromium browser replay and SDK test-app proof artifacts in static:determinism certification mode.',
877
+ 'Automate the Android WebView APK replay harness so the on-device proof stops being manual.',
846
878
  ],
847
879
  referenceDocs: [
848
880
  'external-deterministic-engine-reference:manual/replay',
@@ -767,7 +767,7 @@ function runPhysicsCategory(config) {
767
767
  && physics3DQueries.overlapCovered
768
768
  && physics3DQueries.shapeCastCovered,
769
769
  physics3DMaterialResponse: physics3DMaterials.rollbackEquivalent
770
- && physics3DMaterials.combinedFriction === 3
770
+ && Math.abs(physics3DMaterials.combinedFriction - 0.28284271) < 1e-6
771
771
  && physics3DMaterials.combinedRestitution === 7
772
772
  && physics3DMaterials.normalVelocityAfterBounce < 0,
773
773
  physics3DDetailedQueriesAndContacts: physics3DDetails.rollbackEquivalent
@@ -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.getSnapshot(session.currentFrame).stateBytes.byteLength,
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.withRuntimeDeterminismGuards = exports.createDeterministicRollbackHistory = exports.stableReportJson = exports.stableReportDiffJson = exports.renderDeterministicReportHtml = 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.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 = 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.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; } });
@@ -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");
@@ -294,3 +300,19 @@ Object.defineProperty(exports, "millisecondsToFrames", { enumerable: true, get:
294
300
  Object.defineProperty(exports, "popDueFrameEvents", { enumerable: true, get: function () { return timers_js_1.popDueFrameEvents; } });
295
301
  Object.defineProperty(exports, "scheduleFrameEvent", { enumerable: true, get: function () { return timers_js_1.scheduleFrameEvent; } });
296
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
- checksum: String((0, canonical_js_1.defaultChecksum)({ tick, inputs })),
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
+ }
@@ -22,15 +22,10 @@ function runInstantReplay(options) {
22
22
  options.onFrame(session.getState(), session.currentFrame);
23
23
  expectedFrame += 1;
24
24
  }
25
- const finalSnapshot = session.getSnapshot(session.currentFrame);
26
- /* istanbul ignore if -- the session records a snapshot on every stepped frame. */
27
- if (finalSnapshot === undefined) {
28
- throw new errors_js_1.SyncplayError('Instant replay session has no snapshot at its final frame', {
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
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCAL_AUTHORITY_SNAPSHOT_CHUNK_BYTES = void 0;
3
4
  exports.runDeterministicLocalAuthorityServerRunner = runDeterministicLocalAuthorityServerRunner;
4
5
  const node_http_1 = require("node:http");
5
6
  const canonical_js_1 = require("./canonical.js");
@@ -8,6 +9,7 @@ const commands_js_1 = require("./commands.js");
8
9
  const local_authority_wire_js_1 = require("./local-authority-wire.js");
9
10
  const lag_compensation3d_js_1 = require("./lag-compensation3d.js");
10
11
  const LOCAL_AUTHORITY_MAX_BODY_BYTES = 256 * 1024;
12
+ exports.LOCAL_AUTHORITY_SNAPSHOT_CHUNK_BYTES = 32 * 1024;
11
13
  async function runDeterministicLocalAuthorityServerRunner(options = {}) {
12
14
  const players = options.players ?? 4;
13
15
  const seed = options.seed ?? 71;
@@ -550,7 +552,7 @@ async function driveAuthorityWireResync(baseUrl, runtime, players, seed, sequenc
550
552
  wireRequests += 1;
551
553
  wireSnapshotRequestEnvelopes += 1;
552
554
  wireAuthRejected += request.wire?.rejectionReasons.includes('missing-wire-auth') === true ? 1 : 0;
553
- const transfer = (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotTransfer)(runtime, identity, `runner-transfer-${seed}-${sequenceBase}`, 512);
555
+ const transfer = (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotTransfer)(runtime, identity, `runner-transfer-${seed}-${sequenceBase}`, exports.LOCAL_AUTHORITY_SNAPSHOT_CHUNK_BYTES);
554
556
  const offer = await postWire(baseUrl, 0, (0, local_authority_wire_js_1.createDeterministicLocalAuthoritySnapshotOfferEnvelope)(sequence, transfer, authoritySign));
555
557
  sequence += 1;
556
558
  requests += 1;
@@ -37,10 +37,12 @@ function encodeDeterministicLocalAuthorityWireEnvelope(envelope, sign) {
37
37
  };
38
38
  const auth = sign === undefined ? undefined : createDeterministicLocalAuthorityWireAuth(withoutChecksum, sign);
39
39
  const signedEnvelope = auth === undefined ? withoutChecksum : { ...withoutChecksum, auth };
40
- return (0, canonical_js_1.canonicalStringify)({
40
+ const encoded = (0, canonical_js_1.canonicalStringify)({
41
41
  ...signedEnvelope,
42
42
  checksum: (0, canonical_js_1.defaultChecksum)(signedEnvelope),
43
43
  });
44
+ assertWireEnvelopeWithinBudget(envelope.kind, encoded);
45
+ return encoded;
44
46
  }
45
47
  function decodeDeterministicLocalAuthorityWireEnvelope(encoded) {
46
48
  // Bound parse cost BEFORE JSON.parse. The kind-specific budget is only known
@@ -1267,6 +1269,23 @@ const absoluteMaxWireEnvelopeBytes = 2_097_152;
1267
1269
  function maxWireEnvelopeBytes(kind) {
1268
1270
  return kind === 'input-bundle' || kind === 'command-bundle' ? 65_536 : 2_097_152;
1269
1271
  }
1272
+ // UTF-8 byte length is at most 3× the UTF-16 length, so every small envelope
1273
+ // (per-frame input/command bundles) skips the exact byte measurement.
1274
+ function assertWireEnvelopeWithinBudget(kind, encoded) {
1275
+ const budget = maxWireEnvelopeBytes(kind);
1276
+ if (encoded.length * 3 <= budget) {
1277
+ return;
1278
+ }
1279
+ if (encoded.length > budget || (0, canonical_js_1.encodeUtf8)(encoded).byteLength > budget) {
1280
+ throw new Error(wireEnvelopeBudgetError(kind, budget));
1281
+ }
1282
+ }
1283
+ function wireEnvelopeBudgetError(kind, budget) {
1284
+ if (kind === 'snapshot' || kind === 'correction') {
1285
+ return `Wire ${kind} envelope exceeds the ${budget} byte monolithic budget. A state this large must ride the chunked snapshot-offer/chunk/complete transfer (createSnapshotTransferFromBytes + createDeterministicLocalAuthoritySnapshotOfferEnvelope), which carries snapshots up to the collector's maxSnapshotBytes (default 4194304).`;
1286
+ }
1287
+ return `Wire envelope exceeds ${budget} byte payload budget`;
1288
+ }
1270
1289
  function parseWireAuth(value) {
1271
1290
  if (!isRecord(value)) {
1272
1291
  throw new Error('Wire envelope auth must be an object');
@@ -4,6 +4,8 @@ exports.verifyScoredMatchLog = verifyScoredMatchLog;
4
4
  exports.matchLogSessionFactory = matchLogSessionFactory;
5
5
  const offline_session_js_1 = require("./offline-session.js");
6
6
  const canonical_js_1 = require("./canonical.js");
7
+ const secret_command_classification_js_1 = require("./secret-command-classification.js");
8
+ const secret_protocol_js_1 = require("./secret-protocol.js");
7
9
  function verifyScoredMatchLog(config) {
8
10
  const { log } = config;
9
11
  // The log must be the complete, gap-free tick 0..confirmedThrough sequence —
@@ -13,9 +15,34 @@ function verifyScoredMatchLog(config) {
13
15
  }
14
16
  const session = config.createSession({ seed: log.seed, playerCount: log.playerCount });
15
17
  for (const frame of log.frames) {
18
+ const commands = frame.commands ?? [];
19
+ for (const command of commands) {
20
+ if (!(0, secret_protocol_js_1.isSyncplaySecretCommand)(command)) {
21
+ if ((0, secret_command_classification_js_1.hasSyncplaySecretCommandPrefix)(command)) {
22
+ return {
23
+ ok: false,
24
+ reason: 'authority-command-invalid',
25
+ submittedScore: config.submittedScore,
26
+ framesReplayed: frame.tick,
27
+ };
28
+ }
29
+ continue;
30
+ }
31
+ if (config.expectedAuthorityId === undefined || config.verifyAuthorityCommand === undefined
32
+ || command.authorityId !== config.expectedAuthorityId
33
+ || !config.verifyAuthorityCommand(command, config.expectedAuthorityId)) {
34
+ return {
35
+ ok: false,
36
+ reason: 'authority-command-invalid',
37
+ submittedScore: config.submittedScore,
38
+ framesReplayed: frame.tick,
39
+ };
40
+ }
41
+ }
16
42
  for (let slot = 0; slot < log.playerCount; slot += 1) {
17
43
  session.setInput(slot, config.decodeInput(frame.inputs[slot]));
18
44
  }
45
+ session.recordCommands(commands);
19
46
  session.stepFrames(1);
20
47
  }
21
48
  const finalChecksum = String((0, canonical_js_1.defaultChecksum)(session.getState()));
@@ -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,