@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.
- package/README.md +61 -6
- package/dist/browser.d.ts +86 -57
- package/dist/browser.js +46 -53
- package/dist/certification.d.ts +20 -3
- package/dist/certification.js +78 -2
- package/dist/cjs/browser.js +259 -90
- package/dist/cjs/certification.js +78 -2
- package/dist/cjs/creator.js +2 -1
- package/dist/cjs/engine-complete.js +213 -3
- package/dist/cjs/engine-parity-matrix.js +19 -4
- package/dist/cjs/index.js +193 -139
- package/dist/cjs/movement3d.js +230 -1
- package/dist/cjs/node.js +3 -1
- package/dist/cjs/physics-cert.js +147 -6
- package/dist/cjs/physics2d.js +10 -4
- package/dist/cjs/physics3d-joints.js +637 -0
- package/dist/cjs/physics3d-shared.js +288 -0
- package/dist/cjs/physics3d-solver.js +1351 -0
- package/dist/cjs/physics3d-vehicle.js +467 -0
- package/dist/cjs/physics3d.js +1057 -223
- package/dist/cjs/replay-bundle.js +127 -20
- package/dist/cjs/sample-scenes.js +3 -0
- package/dist/cjs/testing.js +107 -0
- package/dist/cli.js +17 -9
- package/dist/creator.d.ts +1 -1
- package/dist/creator.js +1 -1
- package/dist/engine-complete.js +214 -4
- package/dist/engine-parity-matrix.js +19 -4
- package/dist/index.d.ts +93 -90
- package/dist/index.js +44 -56
- package/dist/movement3d.d.ts +73 -0
- package/dist/movement3d.js +229 -1
- package/dist/node.d.ts +2 -0
- package/dist/node.js +1 -0
- package/dist/physics-cert.d.ts +1 -1
- package/dist/physics-cert.js +147 -6
- package/dist/physics2d.js +10 -4
- package/dist/physics3d-joints.d.ts +94 -0
- package/dist/physics3d-joints.js +634 -0
- package/dist/physics3d-shared.d.ts +72 -0
- package/dist/physics3d-shared.js +257 -0
- package/dist/physics3d-solver.d.ts +197 -0
- package/dist/physics3d-solver.js +1346 -0
- package/dist/physics3d-vehicle.d.ts +84 -0
- package/dist/physics3d-vehicle.js +463 -0
- package/dist/physics3d.d.ts +108 -8
- package/dist/physics3d.js +1006 -177
- package/dist/replay-bundle.d.ts +40 -1
- package/dist/replay-bundle.js +126 -20
- package/dist/sample-scenes.js +3 -0
- package/dist/testing.d.ts +52 -0
- package/dist/testing.js +45 -0
- 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
|
|
1276
|
-
&& materials.tangentVelocityAfterFrictionY
|
|
1277
|
-
&& materials.tangentVelocityAfterFrictionZ
|
|
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
|
-
'
|
|
634
|
-
'
|
|
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
|
-
'
|
|
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',
|