@series-inc/rundot-syncplay 5.23.0-beta.8 → 5.23.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.
Files changed (53) hide show
  1. package/README.md +61 -6
  2. package/dist/browser.d.ts +86 -57
  3. package/dist/browser.js +46 -53
  4. package/dist/certification.d.ts +20 -3
  5. package/dist/certification.js +78 -2
  6. package/dist/cjs/browser.js +259 -90
  7. package/dist/cjs/certification.js +78 -2
  8. package/dist/cjs/creator.js +2 -1
  9. package/dist/cjs/engine-complete.js +213 -3
  10. package/dist/cjs/engine-parity-matrix.js +19 -4
  11. package/dist/cjs/index.js +193 -139
  12. package/dist/cjs/movement3d.js +230 -1
  13. package/dist/cjs/node.js +3 -1
  14. package/dist/cjs/physics-cert.js +147 -6
  15. package/dist/cjs/physics2d.js +10 -4
  16. package/dist/cjs/physics3d-joints.js +637 -0
  17. package/dist/cjs/physics3d-shared.js +288 -0
  18. package/dist/cjs/physics3d-solver.js +1351 -0
  19. package/dist/cjs/physics3d-vehicle.js +467 -0
  20. package/dist/cjs/physics3d.js +1057 -223
  21. package/dist/cjs/replay-bundle.js +127 -20
  22. package/dist/cjs/sample-scenes.js +3 -0
  23. package/dist/cjs/testing.js +107 -0
  24. package/dist/cli.js +17 -9
  25. package/dist/creator.d.ts +1 -1
  26. package/dist/creator.js +1 -1
  27. package/dist/engine-complete.js +214 -4
  28. package/dist/engine-parity-matrix.js +19 -4
  29. package/dist/index.d.ts +93 -90
  30. package/dist/index.js +44 -56
  31. package/dist/movement3d.d.ts +73 -0
  32. package/dist/movement3d.js +229 -1
  33. package/dist/node.d.ts +2 -0
  34. package/dist/node.js +1 -0
  35. package/dist/physics-cert.d.ts +1 -1
  36. package/dist/physics-cert.js +147 -6
  37. package/dist/physics2d.js +10 -4
  38. package/dist/physics3d-joints.d.ts +94 -0
  39. package/dist/physics3d-joints.js +634 -0
  40. package/dist/physics3d-shared.d.ts +72 -0
  41. package/dist/physics3d-shared.js +257 -0
  42. package/dist/physics3d-solver.d.ts +197 -0
  43. package/dist/physics3d-solver.js +1346 -0
  44. package/dist/physics3d-vehicle.d.ts +84 -0
  45. package/dist/physics3d-vehicle.js +463 -0
  46. package/dist/physics3d.d.ts +108 -8
  47. package/dist/physics3d.js +1006 -177
  48. package/dist/replay-bundle.d.ts +40 -1
  49. package/dist/replay-bundle.js +126 -20
  50. package/dist/sample-scenes.js +3 -0
  51. package/dist/testing.d.ts +52 -0
  52. package/dist/testing.js +45 -0
  53. package/package.json +11 -3
@@ -23,6 +23,7 @@ const multiclient_authority_js_1 = require("./multiclient-authority.js");
23
23
  const movement3d_js_1 = require("./movement3d.js");
24
24
  const navmesh_js_1 = require("./navmesh.js");
25
25
  const physics3d_js_1 = require("./physics3d.js");
26
+ const physics3d_vehicle_js_1 = require("./physics3d-vehicle.js");
26
27
  const signals_js_1 = require("./signals.js");
27
28
  const sparse_set_js_1 = require("./sparse-set.js");
28
29
  const system_lifecycle_js_1 = require("./system-lifecycle.js");
@@ -1222,6 +1223,11 @@ function physicsCategory(bodies, requestedFrames, executedFrames, enforceThresho
1222
1223
  const lagComp3DApi = (0, lag_compensation3d_js_1.runDeterministicLagCompensation3DApiFixture)();
1223
1224
  const authoritativeLagComp3D = (0, lag_compensation3d_js_1.runDeterministicAuthoritativeLagCompensation3DFixture)();
1224
1225
  const fullStep = runPhysics3DFullStepMeasurementFixture(Math.min(requestedFrames, 300));
1226
+ // 128-dynamic-body interacting-pile timing sample. Only sampled on long
1227
+ // benchmark runs (>= 2000 frames) so its fixed wall-clock cost amortizes below
1228
+ // the per-frame mean budget; short ad-hoc runs skip it (gates default true).
1229
+ const rigidPile = benchmark && requestedFrames >= 2000 ? runPhysics3DRigidPileMeasurementFixture(128, 200) : undefined;
1230
+ const vehicleFleet = benchmark && requestedFrames >= 2000 ? runVehicle3DFleetMeasurementFixture(16, 200) : undefined;
1225
1231
  const highBodySample512 = requestedFrames > 512 ? (0, physics3d_js_1.runDeterministicPhysics3DStress)(512, 128) : undefined;
1226
1232
  const highBodySample2048 = requestedFrames > 512 ? (0, physics3d_js_1.runDeterministicPhysics3DStress)(2048, 32) : undefined;
1227
1233
  return categoryReport('physics3d', (0, canonical_js_1.defaultChecksum)({
@@ -1270,11 +1276,15 @@ function physicsCategory(bodies, requestedFrames, executedFrames, enforceThresho
1270
1276
  && stack.settledDynamicBodies === stack.stackLayers,
1271
1277
  stackRollbackEquivalent: stack.rollbackEquivalent,
1272
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.
1273
1282
  materialResponseCovered: materials.combinedFriction === 3
1274
1283
  && materials.combinedRestitution === 7
1275
- && materials.normalVelocityAfterBounce === -2
1276
- && materials.tangentVelocityAfterFrictionY === 7
1277
- && materials.tangentVelocityAfterFrictionZ === -7,
1284
+ && materials.normalVelocityAfterBounce > 0
1285
+ && materials.tangentVelocityAfterFrictionY < 10
1286
+ && materials.tangentVelocityAfterFrictionZ > -10
1287
+ && materials.tangentVelocityAfterFrictionZ < 0,
1278
1288
  materialRollbackEquivalent: materials.rollbackEquivalent,
1279
1289
  callbackLifecycleCovered: callbackMasks.collisionEnter > 0
1280
1290
  && callbackMasks.collisionStay > 0
@@ -1314,6 +1324,17 @@ function physicsCategory(bodies, requestedFrames, executedFrames, enforceThresho
1314
1324
  && fullStep.p95MeasuredStepMs > 0
1315
1325
  && fullStep.p99MeasuredStepMs > 0,
1316
1326
  fullStepRollbackEquivalent: fullStep.rollbackEquivalent,
1327
+ rigidPileMeasuredTimingCovered: rigidPile === undefined ? true : rigidPile.bodies === 128 && rigidPile.p99MeasuredStepMs > 0,
1328
+ rigidPileRollbackEquivalent: rigidPile === undefined ? true : rigidPile.rollbackEquivalent,
1329
+ vehicleFleetMeasuredTimingCovered: vehicleFleet === undefined
1330
+ ? true
1331
+ : vehicleFleet.vehicles === 16 && vehicleFleet.groundedWheelSamples > 0 && vehicleFleet.p99MeasuredStepMs > 0,
1332
+ vehicleFleetRollbackEquivalent: vehicleFleet === undefined ? true : vehicleFleet.rollbackEquivalent,
1333
+ vehicleFleetP99WithinBudget: enforceThresholds && vehicleFleet !== undefined ? vehicleFleet.p99MeasuredStepMs <= 25 : true,
1334
+ // Measured wall-clock (never in checksummed state). The pooled clip scratch
1335
+ // + constraint pool (provenance pass 6) pulled the burst p99 to ~1.5 ms
1336
+ // locally; the 10 ms gate keeps generous headroom for CI variance.
1337
+ rigidPileP99WithinBudget: enforceThresholds && rigidPile !== undefined ? rigidPile.p99MeasuredStepMs <= 10 : true,
1317
1338
  fullStepContactsAndQueriesCovered: fullStep.contactCount > 0 && fullStep.queryCount === fullStep.frames,
1318
1339
  executedRequestedFrames: metrics.frames === requestedFrames,
1319
1340
  ...(metrics.extrapolatedStress ? {} : {
@@ -1440,6 +1461,13 @@ function physicsCategory(bodies, requestedFrames, executedFrames, enforceThresho
1440
1461
  highBodySample512Frames: highBodySample512?.frames ?? 0,
1441
1462
  highBodySample2048Checksum: highBodySample2048?.checksum ?? '',
1442
1463
  highBodySample2048Frames: highBodySample2048?.frames ?? 0,
1464
+ vehicleFleetChecksum: vehicleFleet?.checksum ?? '',
1465
+ vehicleFleetVehicles: vehicleFleet?.vehicles ?? 0,
1466
+ vehicleFleetFrames: vehicleFleet?.frames ?? 0,
1467
+ vehicleFleetGroundedWheelSamples: vehicleFleet?.groundedWheelSamples ?? 0,
1468
+ vehicleFleetP95MeasuredStepMs: vehicleFleet?.p95MeasuredStepMs ?? 0,
1469
+ vehicleFleetP99MeasuredStepMs: vehicleFleet?.p99MeasuredStepMs ?? 0,
1470
+ vehicleFleetMaxMeasuredStepMs: vehicleFleet?.maxMeasuredStepMs ?? 0,
1443
1471
  }, requestedFrames, metrics.frames));
1444
1472
  }
1445
1473
  function kccCategory(players, requestedFrames, executedFrames, benchmark, noFastStress) {
@@ -1621,6 +1649,188 @@ function runPhysics3DFullStepMeasurementFixture(frames) {
1621
1649
  wallClockElapsedMs: roundMetric(engineCompleteNowMs() - startedAt, 4),
1622
1650
  };
1623
1651
  }
1652
+ function createRigidPileBodies3D(bodies) {
1653
+ const out = [{
1654
+ id: 'pile-floor', kind: 'static', x: 0, y: 0, z: 0, vx: 0, vy: 0, vz: 0,
1655
+ shape: { type: 'box', halfX: 40, halfY: 0.5, halfZ: 40 }, layer: 1, mask: 1,
1656
+ material: { friction: 8, restitution: 0 },
1657
+ }];
1658
+ const perRow = 8;
1659
+ for (let index = 0; index < bodies; index += 1) {
1660
+ const column = index % perRow;
1661
+ const rowInLayer = Math.floor(index / perRow) % perRow;
1662
+ const layer = Math.floor(index / (perRow * perRow));
1663
+ out.push({
1664
+ id: `pile-${String(index).padStart(3, '0')}`,
1665
+ kind: 'dynamic',
1666
+ x: (column - perRow / 2) * 1.1,
1667
+ y: 1 + layer * 1.05,
1668
+ z: (rowInLayer - perRow / 2) * 1.1,
1669
+ vx: 0, vy: 0, vz: 0, mass: 1,
1670
+ orientation: { x: 0, y: 0, z: 0, w: 1 }, angularVel: { x: 0, y: 0, z: 0 },
1671
+ shape: { type: 'box', halfX: 0.5, halfY: 0.5, halfZ: 0.5 }, layer: 1, mask: 1,
1672
+ material: { friction: 8, restitution: 0 },
1673
+ });
1674
+ }
1675
+ return out;
1676
+ }
1677
+ /**
1678
+ * 128-dynamic-body interacting-pile step-cost sample, timed via wall clock
1679
+ * (never enters checksummed state). CI-gated at measured p99 <= 10 ms
1680
+ * (rigidPileP99WithinBudget) — headroom over the ~1.5 ms local burst cost
1681
+ * after the pooled clip scratch + constraint pool (provenance pass 6).
1682
+ */
1683
+ function runPhysics3DRigidPileMeasurementFixture(bodies, frames) {
1684
+ const initial = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)(createRigidPileBodies3D(bodies));
1685
+ let world = initial;
1686
+ let snapshot = initial;
1687
+ const timings = [];
1688
+ const half = Math.floor(frames / 2);
1689
+ for (let frame = 0; frame < frames; frame += 1) {
1690
+ if (frame === half) {
1691
+ snapshot = world;
1692
+ }
1693
+ const stepStartedAt = engineCompleteNowMs();
1694
+ world = (0, physics3d_js_1.stepDeterministicPhysicsWorld3D)(world, { gravityY: -0.02, computeChecksum: false }).world;
1695
+ timings.push(engineCompleteNowMs() - stepStartedAt);
1696
+ }
1697
+ const checksum = (0, canonical_js_1.defaultChecksum)(world);
1698
+ let restored = snapshot;
1699
+ for (let frame = half; frame < frames; frame += 1) {
1700
+ restored = (0, physics3d_js_1.stepDeterministicPhysicsWorld3D)(restored, { gravityY: -0.02, computeChecksum: false }).world;
1701
+ }
1702
+ return {
1703
+ checksum,
1704
+ rollbackEquivalent: checksum === (0, canonical_js_1.defaultChecksum)(restored),
1705
+ bodies: world.bodies.filter((body) => body.kind === 'dynamic').length,
1706
+ frames,
1707
+ p95MeasuredStepMs: roundMetric(percentileNumber(timings, 95), 6),
1708
+ p99MeasuredStepMs: roundMetric(percentileNumber(timings, 99), 6),
1709
+ maxMeasuredStepMs: roundMetric(timings.reduce((max, value) => Math.max(max, value), 0), 6),
1710
+ };
1711
+ }
1712
+ function fleetVehicleWheel(localAnchor, steered) {
1713
+ return {
1714
+ localAnchor,
1715
+ suspensionDir: { x: 0, y: -1, z: 0 },
1716
+ suspensionRestLength: 0.6,
1717
+ suspensionMaxTravel: 0.3,
1718
+ radius: 0.3,
1719
+ stiffness: 60,
1720
+ damping: 220,
1721
+ grip: 1,
1722
+ maxSteerAngleTurns: 0.1,
1723
+ isSteered: steered,
1724
+ isDriven: steered,
1725
+ };
1726
+ }
1727
+ /**
1728
+ * Measured wall-clock sample of an N-car raycast-vehicle fleet running the full
1729
+ * controller→impulse→physics loop per frame. Timings never enter checksummed
1730
+ * state; the p99 is gated within the global 25 ms budget.
1731
+ */
1732
+ function runVehicle3DFleetMeasurementFixture(vehicleCount, frames) {
1733
+ const bodies = [{
1734
+ id: 'fleet-ground',
1735
+ kind: 'static',
1736
+ x: 0,
1737
+ y: 0,
1738
+ z: 0,
1739
+ vx: 0,
1740
+ vy: 0,
1741
+ vz: 0,
1742
+ shape: { type: 'box', halfX: 200, halfY: 0.5, halfZ: 200 },
1743
+ layer: 1,
1744
+ mask: 1,
1745
+ material: { friction: 8, restitution: 0 },
1746
+ }];
1747
+ const vehicles = [];
1748
+ for (let index = 0; index < vehicleCount; index += 1) {
1749
+ const id = `fleet-car-${index}`;
1750
+ bodies.push({
1751
+ id,
1752
+ kind: 'dynamic',
1753
+ x: (index % 4) * 12 - 18,
1754
+ y: 2.55,
1755
+ z: Math.floor(index / 4) * 12 - 18,
1756
+ vx: 0,
1757
+ vy: 0,
1758
+ vz: 0,
1759
+ mass: 1200,
1760
+ orientation: { x: 0, y: 0, z: 0, w: 1 },
1761
+ angularVel: { x: 0, y: 0, z: 0 },
1762
+ shape: { type: 'box', halfX: 1, halfY: 0.25, halfZ: 2 },
1763
+ layer: 1,
1764
+ mask: 1,
1765
+ });
1766
+ vehicles.push({
1767
+ chassisId: id,
1768
+ driveForce: 12,
1769
+ brakeForce: 40,
1770
+ wheels: [
1771
+ fleetVehicleWheel({ x: -0.9, y: -0.25, z: 1.5 }, true),
1772
+ fleetVehicleWheel({ x: 0.9, y: -0.25, z: 1.5 }, true),
1773
+ fleetVehicleWheel({ x: -0.9, y: -0.25, z: -1.5 }, false),
1774
+ fleetVehicleWheel({ x: 0.9, y: -0.25, z: -1.5 }, false),
1775
+ ],
1776
+ });
1777
+ }
1778
+ const stepFleetFrame = (world, frame) => {
1779
+ // every controller reads the same pre-impulse world (pure reads), then all
1780
+ // impulses land in one batched canonical rebuild — the recommended frame
1781
+ // shape for multi-vehicle games
1782
+ let groundedWheels = 0;
1783
+ const frameImpulses = [];
1784
+ for (let index = 0; index < vehicles.length; index += 1) {
1785
+ const input = {
1786
+ throttle: 10,
1787
+ steer: (Math.trunc(frame / 24) + index) % 2 === 0 ? 10 : -10,
1788
+ brake: 0,
1789
+ };
1790
+ const result = (0, physics3d_vehicle_js_1.stepDeterministicVehicle3D)(vehicles[index], input, world, { dtTicks: 1 });
1791
+ groundedWheels += result.wheels.filter((wheel) => wheel.grounded).length;
1792
+ for (const impulse of result.impulses) {
1793
+ frameImpulses.push({ bodyId: impulse.bodyId, impulse: impulse.impulse, worldPoint: impulse.point });
1794
+ }
1795
+ }
1796
+ const next = (0, physics3d_js_1.stepDeterministicPhysicsWorld3D)((0, physics3d_js_1.applyDeterministicImpulses3D)(world, frameImpulses), {
1797
+ gravityY: -0.02,
1798
+ computeChecksum: false,
1799
+ }).world;
1800
+ return { world: next, groundedWheels };
1801
+ };
1802
+ const initial = (0, physics3d_js_1.createDeterministicPhysicsWorld3D)(bodies);
1803
+ let world = initial;
1804
+ let snapshot = initial;
1805
+ let groundedWheelSamples = 0;
1806
+ const timings = [];
1807
+ const half = Math.floor(frames / 2);
1808
+ for (let frame = 0; frame < frames; frame += 1) {
1809
+ if (frame === half) {
1810
+ snapshot = world;
1811
+ }
1812
+ const stepStartedAt = engineCompleteNowMs();
1813
+ const stepped = stepFleetFrame(world, frame);
1814
+ timings.push(engineCompleteNowMs() - stepStartedAt);
1815
+ world = stepped.world;
1816
+ groundedWheelSamples += stepped.groundedWheels;
1817
+ }
1818
+ const checksum = (0, canonical_js_1.defaultChecksum)(world);
1819
+ let restored = snapshot;
1820
+ for (let frame = half; frame < frames; frame += 1) {
1821
+ restored = stepFleetFrame(restored, frame).world;
1822
+ }
1823
+ return {
1824
+ checksum,
1825
+ rollbackEquivalent: checksum === (0, canonical_js_1.defaultChecksum)(restored),
1826
+ vehicles: vehicles.length,
1827
+ frames,
1828
+ groundedWheelSamples,
1829
+ p95MeasuredStepMs: roundMetric(percentileNumber(timings, 95), 6),
1830
+ p99MeasuredStepMs: roundMetric(percentileNumber(timings, 99), 6),
1831
+ maxMeasuredStepMs: roundMetric(timings.reduce((max, value) => Math.max(max, value), 0), 6),
1832
+ };
1833
+ }
1624
1834
  function runKcc3DFullStepMeasurementFixture(players, frames) {
1625
1835
  let bodies = Array.from({ length: players }, (_, slot) => ({
1626
1836
  x: slot % 16,
@@ -622,19 +622,34 @@ const engineParityMatrixEntries = [
622
622
  currentEvidence: [
623
623
  'packages/syncplay/src/physics2d.ts',
624
624
  'packages/syncplay/src/physics3d.ts',
625
+ 'packages/syncplay/src/physics3d-solver.ts',
626
+ 'packages/syncplay/src/physics3d-joints.ts',
625
627
  'packages/syncplay/tests/physics2d.test.ts',
628
+ 'packages/syncplay/tests/physics3d-solver.test.ts',
629
+ 'packages/syncplay/tests/physics3d-joints.test.ts',
630
+ 'packages/syncplay/tests/physics3d-ccd.test.ts',
626
631
  'packages/syncplay/tests/engine-complete.test.ts',
632
+ 'packages/syncplay/src/physics3d-vehicle.ts',
633
+ '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',
635
+ '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',
627
639
  ],
628
640
  cliProofs: [
629
641
  'npm run lab:physics3d -- --bodies 128 --frames 1000 --no-fast-stress --json artifacts/reports/physics3d-no-fast.json',
630
642
  'npm run certify:engine-complete -- --filter physics3d --players 128 --frames 5000 --no-fast-stress',
643
+ 'npm run lab:physics-cert -- --engine builtin-rigid --shape fixed-arena --players 4 --frames 600 --seed 7 --snapshot-every 8',
644
+ 'npm run coverage:package:physics-vehicle',
631
645
  ],
632
646
  gaps: [
633
- 'Production-quality stacked rigid-body solving, broad mesh import, full CCD, and long no-fast stress are not proven.',
634
- '100k stress can use optimized-tail mode unless no-fast-stress is required.',
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).',
635
650
  ],
636
651
  nextCliWork: [
637
- 'Add adversarial no-fast fixtures for stacks, tunneling, joints, query ordering, 2.5D overload, and rollback equivalence.',
652
+ 'Profile the remaining burst cost before further pooling; no known single lever.',
638
653
  ],
639
654
  referenceDocs: [
640
655
  'external-deterministic-engine-reference:manual/physics/overview',
@@ -798,7 +813,7 @@ const engineParityMatrixEntries = [
798
813
  'Integrated samples need stronger per-scene mechanics assertions and replay artifacts.',
799
814
  ],
800
815
  nextCliWork: [
801
- 'Add CLI-rendered or artifact-traced Simple FPS, platform shooter, arena brawler, nav agents, animator, KCC playground, vehicle, and waiting-room samples.',
816
+ 'Add CLI-rendered or artifact-traced Simple FPS, platform shooter, arena brawler, nav agents, animator, KCC playground, vehicle (the sample-scenes vehicle is kinematic by design; raycast-wheel physics lives in physics3d-vehicle.ts), and waiting-room samples.',
802
817
  ],
803
818
  referenceDocs: [
804
819
  'external-deterministic-engine-reference:game-samples/engine-simple-fps/overview',