@series-inc/rundot-syncplay 5.28.0-beta.2 → 5.28.0-beta.4

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/core/physics.d.ts CHANGED
@@ -276,6 +276,7 @@ export interface KinetixPhysicsStepResult {
276
276
  readonly world: KinetixPhysicsWorld;
277
277
  readonly contacts: readonly KinetixPhysicsContactEvent[];
278
278
  readonly brokenJointIds: readonly string[];
279
+ readonly brokenJoints: readonly string[];
279
280
  readonly scheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
280
281
  readonly prePhysicsScheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
281
282
  readonly postPhysicsScheduledQueryResults: readonly { readonly id: string; readonly hits: readonly KinetixPhysicsHit[] }[];
@@ -2953,6 +2953,7 @@ export function stepPhysicsWorld(world, options) {
2953
2953
  world: nextWorld,
2954
2954
  contacts: visibleEvents,
2955
2955
  brokenJointIds,
2956
+ brokenJoints: brokenJointIds,
2956
2957
  scheduledQueryResults,
2957
2958
  prePhysicsScheduledQueryResults,
2958
2959
  postPhysicsScheduledQueryResults,
package/dist/browser.d.ts CHANGED
@@ -25,7 +25,7 @@ export { createSyncplayRunner } from './runner.js';
25
25
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
26
26
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
27
27
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
28
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
28
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
29
29
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
30
30
  export { cylinderHullVertices } from './physics3d-hull.js';
31
31
  export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
@@ -66,8 +66,8 @@ export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, Determinist
66
66
  export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, NetworkedSyncplaySlotPresence, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode, SyncplayRemoteInputPrediction } from './networked-client.js';
67
67
  export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOccupancy, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
68
68
  export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsActivePair2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
69
- export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
70
- export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
69
+ export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
70
+ export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
71
71
  export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
72
72
  export type { DeterministicAuthoritativeBodyProxy3D, DeterministicCosmeticPhysicsBody3D, DeterministicCosmeticPhysicsStepOptions3D, DeterministicCosmeticPhysicsStepResult3D, DeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
73
73
  export type { DeterministicPhysicsJoint3D } from './physics3d-joints.js';
package/dist/browser.js CHANGED
@@ -26,7 +26,7 @@ export { createSyncplayRunner } from './runner.js';
26
26
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
27
27
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
28
28
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
29
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
29
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
30
30
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
31
31
  export { cylinderHullVertices } from './physics3d-hull.js';
32
32
  export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
package/dist/creator.d.ts CHANGED
@@ -13,13 +13,13 @@ export type { ReplayRuntimeFactory, ReplayVerificationConfig, ReplayVerification
13
13
  export type { SyncplaySession, SyncplaySessionPolicy } from './sdk-session.js';
14
14
  export type { InstantReplayInputFrame, InstantReplayResult, InstantReplayRunner, InstantReplayRunnerOptions, InstantReplayRunOptions, RunInstantReplayOptions, } from './instant-replay.js';
15
15
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
16
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, } from './physics3d-voxel.js';
16
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D, } from './physics3d-voxel.js';
17
17
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
18
18
  export { cylinderHullVertices } from './physics3d-hull.js';
19
19
  export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
20
20
  export { createDeterministicBinarySerializer, deterministicDslMaxPlayers, parseDeterministicSchemaDsl, stableDeterministicDslSchemaJson, } from './schema-dsl-binary.js';
21
- export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D, } from './physics3d.js';
22
- export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D, } from './physics3d-voxel.js';
21
+ export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D, } from './physics3d.js';
22
+ export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D, } from './physics3d-voxel.js';
23
23
  export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
24
24
  export type { DeterministicPhysicsJoint3D } from './physics3d-joints.js';
25
25
  export type { DeterministicVehicle3D, DeterministicVehicleImpulse3D, DeterministicVehicleInput3D, DeterministicVehicleStepOptions3D, DeterministicVehicleStepResult3D, DeterministicVehicleWheel3D, DeterministicVehicleWheelTelemetry3D, } from './physics3d-vehicle.js';
package/dist/creator.js CHANGED
@@ -10,7 +10,7 @@ export { runSyncplaySynctest } from './synctest.js';
10
10
  // cooking. Cosmetic physics, KCC (movement3d), and the destruction-cert runner
11
11
  // stay off it — the first two are not requested, the third reads wall clock.
12
12
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
13
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, } from './physics3d-voxel.js';
13
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D, } from './physics3d-voxel.js';
14
14
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
15
15
  export { cylinderHullVertices } from './physics3d-hull.js';
16
16
  export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ export { createSyncplayRunner } from './runner.js';
28
28
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
29
29
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
30
30
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
31
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
31
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
32
32
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
33
33
  export { cylinderHullVertices } from './physics3d-hull.js';
34
34
  export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
@@ -78,8 +78,8 @@ export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, Determinist
78
78
  export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode, SyncplayRemoteInputPrediction } from './networked-client.js';
79
79
  export type { SyncplayRunner, SyncplayRunnerConfig, SyncplayRunnerConnectionEvent, SyncplayRunnerOccupancy, SyncplayRunnerOfflineOptions, SyncplayRunnerPresentation, SyncplayRunnerSnapshot, SyncplayRunnerStatus, SyncplayRunnerTransport } from './runner.js';
80
80
  export type { DeterministicBodyKind, DeterministicContactEvent2D, DeterministicContactEventType, DeterministicPhysicsAabbQuery2D, DeterministicPhysicsBody2D, DeterministicPhysicsCircleCastQuery2D, DeterministicPhysicsConstraint2D, DeterministicPhysicsHit2D, DeterministicPhysicsQueryOptions2D, DeterministicPhysicsRaycastQuery2D, DeterministicPhysicsShape2D, DeterministicPhysicsShapeOverlapQuery2D, DeterministicPhysicsStepOptions2D, DeterministicPhysicsStepResult2D, DeterministicPhysicsWorld2D } from './physics2d.js';
81
- export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
82
- export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
81
+ export type { DeterministicContactCacheEntry3D, DeterministicFluidVolume3D, DeterministicJointCacheEntry3D, DeterministicKinematicPose3D, DeterministicKinematicTransformBuffer3D, DeterministicPhysicsAabb3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsForceApplication3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepOptions3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D } from './physics3d.js';
82
+ export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D, DeterministicVoxelCompoundSplitResult3D, DeterministicVoxelRayHit3D, SplitDeterministicVoxelChunkOptions3D, VoxelAnchorFace, VoxelBoxCarveRegion3D, VoxelCapsuleCarveRegion3D, VoxelCarveRegion3D, VoxelOccupancy3D, VoxelSphereCarveRegion3D, VoxelSubChunk3D } from './physics3d-voxel.js';
83
83
  export type { PhysicsWorldDebugMesh, PhysicsWorldDebugMeshOptions } from './debug-wireframe.js';
84
84
  export type { DeterministicDestructionCertOptions, DeterministicDestructionCertResult } from './physics3d-destruction-cert.js';
85
85
  export type { DeterministicAuthoritativeBodyProxy3D, DeterministicCosmeticPhysicsBody3D, DeterministicCosmeticPhysicsStepOptions3D, DeterministicCosmeticPhysicsStepResult3D, DeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ export { createSyncplayRunner } from './runner.js';
29
29
  export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js';
30
30
  export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
31
31
  export { applyDeterministicForces3D, applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createBoxShapeFromExtents, createBoxShapeFromMinMax, createCapsuleShapeFromBase, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, setDeterministicKinematicPoses3D, setDeterministicKinematicTransforms3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
32
- export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D } from './physics3d-voxel.js';
32
+ export { carveDeterministicVoxelChunk3D, cookDeterministicVoxelChunkCollider3D, cookDeterministicVoxelSubregion3D, recookDeterministicVoxelSubregion3D, raycastDeterministicVoxelChunk3D, splitDeterministicVoxelChunk3D, splitDeterministicVoxelCompoundCollider3D } from './physics3d-voxel.js';
33
33
  export { generatePhysicsWorldDebugMesh } from './debug-wireframe.js';
34
34
  export { cylinderHullVertices } from './physics3d-hull.js';
35
35
  export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
@@ -101,6 +101,15 @@ export type DeterministicPhysicsJoint3D = {
101
101
  readonly targetZ: number;
102
102
  readonly maxForce: number;
103
103
  readonly damping?: number;
104
+ readonly linearHertz?: number;
105
+ readonly linearDampingRatio?: number;
106
+ readonly targetRotation?: Quat3D;
107
+ readonly maxTorque?: number;
108
+ readonly angularDamping?: number;
109
+ readonly angularHertz?: number;
110
+ readonly angularDampingRatio?: number;
111
+ readonly breakDistance?: number;
112
+ readonly breakImpulse?: number;
104
113
  };
105
114
  /** Build solver joint objects from joint definitions, seeded from the joint cache. */
106
115
  export declare function createSolverJoints(joints: readonly DeterministicPhysicsJoint3D[], indexById: Map<string, number>, jointCache: readonly SolverJointCacheEntry[]): SolverJointSolve[];
@@ -626,6 +626,29 @@ function springJoint(joint, aIndex, bIndex, warm) {
626
626
  },
627
627
  };
628
628
  }
629
+ /**
630
+ * Box3D / Box2D v3 b3MakeSoft coefficient scheme:
631
+ * Translates (frequencyHz, dampingRatio, dt) into mass-independent constraint softness coefficients:
632
+ * biasRate = omega / a1, massScale = a2 / (1 + a2), impulseScale = 1 / (1 + a2).
633
+ * Unconditionally stable: biasRate * dt = (dt * omega) / (2 * damping + dt * omega) <= 1.0 always.
634
+ */
635
+ function makeSoft(hertz, dampingRatio, dt) {
636
+ if (hertz <= 0) {
637
+ return { biasRate: 0, massScale: 0, impulseScale: 0 };
638
+ }
639
+ const omega = TWO_PI * hertz;
640
+ const a1 = 2 * Math.max(0, dampingRatio) + dt * omega;
641
+ if (a1 <= 1e-18) {
642
+ return { biasRate: 0, massScale: 0, impulseScale: 0 };
643
+ }
644
+ const a2 = dt * omega * a1;
645
+ const a3 = 1 / (1 + a2);
646
+ return {
647
+ biasRate: omega / a1,
648
+ massScale: a2 * a3,
649
+ impulseScale: a3,
650
+ };
651
+ }
629
652
  function grabJoint(joint, bodyIndex, warm) {
630
653
  const state = {
631
654
  impulse: warm ? { x: warm.impulseX, y: warm.impulseY, z: warm.impulseZ } : { x: 0, y: 0, z: 0 },
@@ -636,36 +659,139 @@ function grabJoint(joint, bodyIndex, warm) {
636
659
  };
637
660
  let r = { x: 0, y: 0, z: 0 };
638
661
  let kInv = [0, 0, 0, 0, 0, 0, 0, 0, 0];
639
- let desiredVel = { x: 0, y: 0, z: 0 };
640
662
  const target = { x: joint.targetX, y: joint.targetY, z: joint.targetZ };
641
663
  const maxImpulse = joint.maxForce * 1;
664
+ const targetRotation = joint.targetRotation !== undefined ? normalizeQuat(joint.targetRotation) : undefined;
665
+ const maxTorque = joint.maxTorque ?? 0;
666
+ const linearHertz = joint.linearHertz ?? 5.0;
667
+ const linearDampingRatio = joint.linearDampingRatio ?? joint.damping ?? 1.0;
668
+ const angularHertz = joint.angularHertz ?? 5.0;
669
+ const angularDampingRatio = joint.angularDampingRatio ?? joint.angularDamping ?? joint.damping ?? 1.0;
670
+ let linBiasRate = 0;
671
+ let linMassScale = 1;
672
+ let linImpulseScale = 0;
673
+ let angBiasVel = { x: 0, y: 0, z: 0 };
674
+ let angMassScale = 1;
675
+ let angImpulseScale = 0;
676
+ let angMassMat;
677
+ let angMassDiag;
678
+ let broken = false;
679
+ let torqueImpulse = { x: 0, y: 0, z: 0 };
642
680
  return {
643
681
  bodyIndices: [bodyIndex],
644
682
  prepare(ctx) {
683
+ if (broken) {
684
+ return;
685
+ }
686
+ torqueImpulse = { x: 0, y: 0, z: 0 };
645
687
  const body = ctx.bodies[bodyIndex];
646
688
  r = jointLever(body, joint.anchor);
647
689
  const worldAnchor = addVec3(body.c, r);
690
+ if (joint.breakDistance !== undefined) {
691
+ const delta = subVec3(target, worldAnchor);
692
+ const d = solverSqrt(dotVec3(delta, delta));
693
+ if (d > joint.breakDistance) {
694
+ broken = true;
695
+ state.impulse = { x: 0, y: 0, z: 0 };
696
+ return;
697
+ }
698
+ }
648
699
  const imB = ctx.invMass[bodyIndex];
649
700
  const linear = mat3FromDiag(imB);
650
701
  const skewR = skewMat3(r);
651
702
  const ang = mat3Multiply(mat3Multiply(skewR, ctx.invInertia[bodyIndex]), skewR);
652
703
  kInv = invertMat3(mat3Sub(linear, ang));
653
- const damping = joint.damping ?? 1;
654
- desiredVel = scaleVec3(subVec3(target, worldAnchor), damping / ctx.dt);
704
+ const linSoft = (joint.linearHertz !== undefined || joint.linearDampingRatio !== undefined)
705
+ ? makeSoft(linearHertz, linearDampingRatio, ctx.dt)
706
+ : undefined;
707
+ linBiasRate = linSoft ? linSoft.biasRate : (joint.damping ?? 1) / ctx.dt;
708
+ linMassScale = linSoft ? linSoft.massScale : 1.0;
709
+ linImpulseScale = linSoft ? linSoft.impulseScale : 0;
655
710
  ctx.applyToBody(bodyIndex, r, state.impulse);
711
+ if (targetRotation !== undefined) {
712
+ const angSoft = makeSoft(angularHertz, angularDampingRatio, ctx.dt);
713
+ angMassScale = angSoft.massScale;
714
+ angImpulseScale = angSoft.impulseScale;
715
+ const errorRotation = quatMultiply(quatConjugate(targetRotation), body.q);
716
+ const sign = errorRotation.w < 0 ? -1 : 1;
717
+ const error = {
718
+ x: 2 * sign * errorRotation.x,
719
+ y: 2 * sign * errorRotation.y,
720
+ z: 2 * sign * errorRotation.z,
721
+ };
722
+ // Box3D soft constraint bias rate: biasRate * dt <= 1.0 always, preventing single-step overshoot
723
+ angBiasVel = clampMagnitude(scaleVec3(error, -angSoft.biasRate), 10.0);
724
+ const invI = ctx.invInertia[bodyIndex];
725
+ const fullInv = invertMat3(invI);
726
+ const isSingular = fullInv[0] === 0 && fullInv[4] === 0 && fullInv[8] === 0;
727
+ if (!isSingular) {
728
+ angMassMat = fullInv;
729
+ angMassDiag = undefined;
730
+ }
731
+ else {
732
+ angMassMat = undefined;
733
+ angMassDiag = {
734
+ x: invI[0] > 1e-9 ? 1 / invI[0] : 0,
735
+ y: invI[4] > 1e-9 ? 1 / invI[4] : 0,
736
+ z: invI[8] > 1e-9 ? 1 / invI[8] : 0,
737
+ };
738
+ }
739
+ }
656
740
  },
657
741
  iterate(ctx) {
742
+ if (broken) {
743
+ return;
744
+ }
658
745
  const body = ctx.bodies[bodyIndex];
746
+ const worldAnchor = addVec3(body.c, r);
747
+ const posError = subVec3(target, worldAnchor);
748
+ const desiredVel = scaleVec3(posError, linBiasRate);
659
749
  const currentVel = addVec3(body.v, crossVec3(body.w, r));
660
- const dImpulse = mulMat3Vec(kInv, subVec3(desiredVel, currentVel));
750
+ const deltaV = subVec3(desiredVel, currentVel);
751
+ const rawImpulse = mulMat3Vec(kInv, deltaV);
752
+ const dImpulse = subVec3(scaleVec3(rawImpulse, linMassScale), scaleVec3(state.impulse, linImpulseScale));
661
753
  const target2 = addVec3(state.impulse, dImpulse);
662
754
  const clamped = clampMagnitude(target2, maxImpulse * ctx.dt);
663
755
  const applied = subVec3(clamped, state.impulse);
664
756
  state.impulse = clamped;
665
757
  ctx.applyToBody(bodyIndex, r, applied);
758
+ if (targetRotation !== undefined && maxTorque > 0) {
759
+ const deltaW = subVec3(angBiasVel, body.w);
760
+ const rawTorque = angMassMat !== undefined
761
+ ? mulMat3Vec(angMassMat, deltaW)
762
+ : { x: deltaW.x * angMassDiag.x, y: deltaW.y * angMassDiag.y, z: deltaW.z * angMassDiag.z };
763
+ const dTorque = subVec3(scaleVec3(rawTorque, angMassScale), scaleVec3(torqueImpulse, angImpulseScale));
764
+ const targetTorque = addVec3(torqueImpulse, dTorque);
765
+ const clampedTorque = clampMagnitude(targetTorque, maxTorque * ctx.dt);
766
+ let appliedTorque = subVec3(clampedTorque, torqueImpulse);
767
+ // Safeguard angular acceleration: clamp appliedTorque so Delta omega <= 0.5 rad/substep
768
+ const dw = mulMat3Vec(ctx.invInertia[bodyIndex], appliedTorque);
769
+ const dwLen = solverSqrt(dotVec3(dw, dw));
770
+ if (dwLen > 0.5) {
771
+ appliedTorque = scaleVec3(appliedTorque, 0.5 / dwLen);
772
+ }
773
+ torqueImpulse = addVec3(torqueImpulse, appliedTorque);
774
+ ctx.applyAngularToBody(bodyIndex, appliedTorque);
775
+ }
776
+ },
777
+ postSubstep(_ctx) {
778
+ if (!broken) {
779
+ state.stepImpulse = addVec3(state.stepImpulse, state.impulse);
780
+ }
781
+ torqueImpulse = { x: 0, y: 0, z: 0 };
666
782
  },
667
783
  emitCache() {
668
- return { jointId: joint.id, impulseX: state.impulse.x, impulseY: state.impulse.y, impulseZ: state.impulse.z, axialImpulse: 0, hingeAngle: 0 };
784
+ const reaction2 = dotVec3(state.stepImpulse, state.stepImpulse);
785
+ const isBroken = broken || (joint.breakImpulse !== undefined && reaction2 > joint.breakImpulse * joint.breakImpulse);
786
+ return {
787
+ jointId: joint.id,
788
+ impulseX: isBroken ? 0 : state.impulse.x,
789
+ impulseY: isBroken ? 0 : state.impulse.y,
790
+ impulseZ: isBroken ? 0 : state.impulse.z,
791
+ axialImpulse: 0,
792
+ hingeAngle: 0,
793
+ ...(isBroken ? { broken: true } : {}),
794
+ };
669
795
  },
670
796
  };
671
797
  }
@@ -687,6 +813,45 @@ function validateJoint(joint) {
687
813
  if (!Number.isFinite(joint.targetX) || !Number.isFinite(joint.targetY) || !Number.isFinite(joint.targetZ)) {
688
814
  throw new Error(`physics3d joint ${joint.id}: target must be finite`);
689
815
  }
816
+ if (joint.damping !== undefined && (!Number.isFinite(joint.damping) || joint.damping < 0)) {
817
+ throw new Error(`physics3d joint ${joint.id}: damping must be non-negative`);
818
+ }
819
+ if (joint.linearHertz !== undefined && (!Number.isFinite(joint.linearHertz) || joint.linearHertz <= 0)) {
820
+ throw new Error(`physics3d joint ${joint.id}: linearHertz must be positive`);
821
+ }
822
+ if (joint.linearDampingRatio !== undefined && (!Number.isFinite(joint.linearDampingRatio) || joint.linearDampingRatio < 0)) {
823
+ throw new Error(`physics3d joint ${joint.id}: linearDampingRatio must be non-negative`);
824
+ }
825
+ if (joint.targetRotation !== undefined) {
826
+ const target = joint.targetRotation;
827
+ if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(target.w)
828
+ || target.x * target.x + target.y * target.y + target.z * target.z + target.w * target.w === 0) {
829
+ throw new Error(`physics3d joint ${joint.id}: targetRotation must be a finite non-zero quaternion`);
830
+ }
831
+ if (joint.maxTorque === undefined || !Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
832
+ throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
833
+ }
834
+ }
835
+ else if (joint.maxTorque !== undefined) {
836
+ if (!Number.isFinite(joint.maxTorque) || joint.maxTorque <= 0) {
837
+ throw new Error(`physics3d joint ${joint.id}: maxTorque must be a positive finite number`);
838
+ }
839
+ }
840
+ if (joint.angularDamping !== undefined && (!Number.isFinite(joint.angularDamping) || joint.angularDamping < 0)) {
841
+ throw new Error(`physics3d joint ${joint.id}: angularDamping must be non-negative`);
842
+ }
843
+ if (joint.angularHertz !== undefined && (!Number.isFinite(joint.angularHertz) || joint.angularHertz <= 0)) {
844
+ throw new Error(`physics3d joint ${joint.id}: angularHertz must be positive`);
845
+ }
846
+ if (joint.angularDampingRatio !== undefined && (!Number.isFinite(joint.angularDampingRatio) || joint.angularDampingRatio < 0)) {
847
+ throw new Error(`physics3d joint ${joint.id}: angularDampingRatio must be non-negative`);
848
+ }
849
+ if (joint.breakDistance !== undefined && (!Number.isFinite(joint.breakDistance) || joint.breakDistance <= 0)) {
850
+ throw new Error(`physics3d joint ${joint.id}: breakDistance must be positive`);
851
+ }
852
+ if (joint.breakImpulse !== undefined && (!Number.isFinite(joint.breakImpulse) || joint.breakImpulse <= 0)) {
853
+ throw new Error(`physics3d joint ${joint.id}: breakImpulse must be positive`);
854
+ }
690
855
  return;
691
856
  }
692
857
  assertFiniteVec(joint.anchorA, 'anchorA', joint.id);
@@ -141,6 +141,9 @@ export function worldInvInertia(rot, invI) {
141
141
  * identity so no NaN reaches state.
142
142
  */
143
143
  export function integrateQuat(q, w, dt) {
144
+ if (!Number.isFinite(dt) || dt <= 0 || !Number.isFinite(w.x) || !Number.isFinite(w.y) || !Number.isFinite(w.z)) {
145
+ return normalizeQuat(q);
146
+ }
144
147
  const hx = w.x * 0.5 * dt;
145
148
  const hy = w.y * 0.5 * dt;
146
149
  const hz = w.z * 0.5 * dt;
@@ -148,18 +151,34 @@ export function integrateQuat(q, w, dt) {
148
151
  const ny = q.y + (hy * q.w + hz * q.x - hx * q.z);
149
152
  const nz = q.z + (hz * q.w + hx * q.y - hy * q.x);
150
153
  const nw = q.w + (-hx * q.x - hy * q.y - hz * q.z);
151
- const length = solverSqrt(nx * nx + ny * ny + nz * nz + nw * nw);
152
- if (length === 0) {
153
- return { x: 0, y: 0, z: 0, w: 1 };
154
+ const maxComp = Math.max(Math.abs(nx), Math.abs(ny), Math.abs(nz), Math.abs(nw));
155
+ if (!Number.isFinite(maxComp) || maxComp === 0) {
156
+ return normalizeQuat(q);
157
+ }
158
+ const sx = nx / maxComp;
159
+ const sy = ny / maxComp;
160
+ const sz = nz / maxComp;
161
+ const sw = nw / maxComp;
162
+ const length = solverSqrt(sx * sx + sy * sy + sz * sz + sw * sw);
163
+ if (!Number.isFinite(length) || length === 0) {
164
+ return normalizeQuat(q);
154
165
  }
155
- return { x: nx / length, y: ny / length, z: nz / length, w: nw / length };
166
+ return { x: sx / length, y: sy / length, z: sz / length, w: sw / length };
156
167
  }
157
168
  export function normalizeQuat(q) {
158
- const length = solverSqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
159
- if (length === 0) {
169
+ const maxComp = Math.max(Math.abs(q.x), Math.abs(q.y), Math.abs(q.z), Math.abs(q.w));
170
+ if (!Number.isFinite(maxComp) || maxComp === 0) {
171
+ return { x: 0, y: 0, z: 0, w: 1 };
172
+ }
173
+ const sx = q.x / maxComp;
174
+ const sy = q.y / maxComp;
175
+ const sz = q.z / maxComp;
176
+ const sw = q.w / maxComp;
177
+ const length = solverSqrt(sx * sx + sy * sy + sz * sz + sw * sw);
178
+ if (!Number.isFinite(length) || length === 0) {
160
179
  return { x: 0, y: 0, z: 0, w: 1 };
161
180
  }
162
- return { x: q.x / length, y: q.y / length, z: q.z / length, w: q.w / length };
181
+ return { x: sx / length, y: sy / length, z: sz / length, w: sw / length };
163
182
  }
164
183
  export function quatConjugate(q) {
165
184
  return { x: -q.x, y: -q.y, z: -q.z, w: q.w };
@@ -222,6 +222,8 @@ export interface SolverJointContext {
222
222
  apply(a: number, b: number, rA: Point3D, rB: Point3D, impulse: Point3D): void;
223
223
  /** Angular-only impulse (no linear component), e.g. hinge motor/limit rows. */
224
224
  applyAngular(a: number, b: number, angularImpulse: Point3D): void;
225
+ /** Apply an angular impulse to a single body (grab joint orientation drive). */
226
+ applyAngularToBody(index: number, angularImpulse: Point3D): void;
225
227
  /** Apply an impulse to a single body at anchor `r` (grab / world-target joints). */
226
228
  applyToBody(index: number, r: Point3D, impulse: Point3D): void;
227
229
  /** Split-impulse position correction on pseudo-velocities (no real momentum). */
@@ -247,6 +249,24 @@ export interface SolverWarmPoint {
247
249
  readonly tangentImpulse1: number;
248
250
  readonly tangentImpulse2: number;
249
251
  }
252
+ export interface DeterministicPhysicsAabb3D {
253
+ readonly minX: number;
254
+ readonly minY: number;
255
+ readonly minZ: number;
256
+ readonly maxX: number;
257
+ readonly maxY: number;
258
+ readonly maxZ: number;
259
+ }
260
+ export interface DeterministicFluidVolume3D {
261
+ readonly id: string;
262
+ readonly bounds: DeterministicPhysicsAabb3D;
263
+ readonly density?: number;
264
+ readonly linearDrag?: number;
265
+ readonly angularDrag?: number;
266
+ readonly flowVelocity?: Point3D;
267
+ readonly layer?: number;
268
+ readonly mask?: number;
269
+ }
250
270
  export interface SolverStepOptions {
251
271
  readonly dt: number;
252
272
  readonly gravityY: number;
@@ -260,6 +280,7 @@ export interface SolverStepOptions {
260
280
  readonly joints?: readonly SolverJointSolve[];
261
281
  /** Packed index pairs (min * WARM_KEY_BASE + max) whose contacts are suppressed (jointed pairs). */
262
282
  readonly noCollidePairs?: ReadonlySet<number>;
283
+ readonly fluids?: readonly DeterministicFluidVolume3D[];
263
284
  }
264
285
  export declare function combinedContactFriction(frictionA10: number, frictionB10: number): number;
265
286
  export interface SolverContactImpulse {
@@ -1435,6 +1435,286 @@ function takeConstraint() {
1435
1435
  constraintPool.high += 1;
1436
1436
  return slot;
1437
1437
  }
1438
+ function computeSubmergence(b, bounds) {
1439
+ let points = [];
1440
+ let volume = 0;
1441
+ let yMin = b.p.y;
1442
+ let yMax = b.p.y;
1443
+ let xMin = b.p.x;
1444
+ let xMax = b.p.x;
1445
+ let zMin = b.p.z;
1446
+ let zMax = b.p.z;
1447
+ const shape = b.shape;
1448
+ if (shape.kind === 'box') {
1449
+ const h = shape.half;
1450
+ volume = 8 * h.x * h.y * h.z;
1451
+ const corners = [
1452
+ { x: -h.x, y: -h.y, z: -h.z },
1453
+ { x: h.x, y: -h.y, z: -h.z },
1454
+ { x: -h.x, y: h.y, z: -h.z },
1455
+ { x: h.x, y: h.y, z: -h.z },
1456
+ { x: -h.x, y: -h.y, z: h.z },
1457
+ { x: h.x, y: -h.y, z: h.z },
1458
+ { x: -h.x, y: h.y, z: h.z },
1459
+ { x: h.x, y: h.y, z: h.z },
1460
+ ];
1461
+ points = corners.map((c) => addVec3(b.p, rotatePointByQuat(b.q, c)));
1462
+ xMin = points[0].x;
1463
+ xMax = points[0].x;
1464
+ yMin = points[0].y;
1465
+ yMax = points[0].y;
1466
+ zMin = points[0].z;
1467
+ zMax = points[0].z;
1468
+ for (let i = 1; i < points.length; i += 1) {
1469
+ const p = points[i];
1470
+ if (p.x < xMin)
1471
+ xMin = p.x;
1472
+ if (p.x > xMax)
1473
+ xMax = p.x;
1474
+ if (p.y < yMin)
1475
+ yMin = p.y;
1476
+ if (p.y > yMax)
1477
+ yMax = p.y;
1478
+ if (p.z < zMin)
1479
+ zMin = p.z;
1480
+ if (p.z > zMax)
1481
+ zMax = p.z;
1482
+ }
1483
+ }
1484
+ else if (shape.kind === 'sphere') {
1485
+ const r = shape.radius;
1486
+ volume = (4 / 3) * Math.PI * r * r * r;
1487
+ xMin = b.p.x - r;
1488
+ xMax = b.p.x + r;
1489
+ yMin = b.p.y - r;
1490
+ yMax = b.p.y + r;
1491
+ zMin = b.p.z - r;
1492
+ zMax = b.p.z + r;
1493
+ points = [b.p];
1494
+ }
1495
+ else if (shape.kind === 'capsule') {
1496
+ const r = shape.radius;
1497
+ const hh = shape.halfHeight;
1498
+ volume = (4 / 3) * Math.PI * r * r * r + 2 * Math.PI * r * r * hh;
1499
+ const pTop = addVec3(b.p, rotatePointByQuat(b.q, { x: 0, y: hh, z: 0 }));
1500
+ const pBot = addVec3(b.p, rotatePointByQuat(b.q, { x: 0, y: -hh, z: 0 }));
1501
+ xMin = Math.min(pTop.x, pBot.x) - r;
1502
+ xMax = Math.max(pTop.x, pBot.x) + r;
1503
+ yMin = Math.min(pTop.y, pBot.y) - r;
1504
+ yMax = Math.max(pTop.y, pBot.y) + r;
1505
+ zMin = Math.min(pTop.z, pBot.z) - r;
1506
+ zMax = Math.max(pTop.z, pBot.z) + r;
1507
+ points = [pBot, pTop];
1508
+ }
1509
+ else if (shape.kind === 'hull') {
1510
+ const verts = shape.data.vertices;
1511
+ volume = 1.0;
1512
+ points = verts.map((v) => addVec3(b.p, rotatePointByQuat(b.q, v)));
1513
+ if (points.length > 0) {
1514
+ xMin = points[0].x;
1515
+ xMax = points[0].x;
1516
+ yMin = points[0].y;
1517
+ yMax = points[0].y;
1518
+ zMin = points[0].z;
1519
+ zMax = points[0].z;
1520
+ for (let i = 1; i < points.length; i += 1) {
1521
+ const p = points[i];
1522
+ if (p.x < xMin)
1523
+ xMin = p.x;
1524
+ if (p.x > xMax)
1525
+ xMax = p.x;
1526
+ if (p.y < yMin)
1527
+ yMin = p.y;
1528
+ if (p.y > yMax)
1529
+ yMax = p.y;
1530
+ if (p.z < zMin)
1531
+ zMin = p.z;
1532
+ if (p.z > zMax)
1533
+ zMax = p.z;
1534
+ }
1535
+ volume = Math.max(0.001, (xMax - xMin) * (yMax - yMin) * (zMax - zMin) * 0.5);
1536
+ }
1537
+ }
1538
+ else if (shape.kind === 'compound') {
1539
+ volume = 0;
1540
+ for (const child of shape.children) {
1541
+ const childCenter = addVec3(b.p, rotatePointByQuat(b.q, child.offset));
1542
+ const childQ = quatMultiply(b.q, child.q);
1543
+ if (child.shape.kind === 'box') {
1544
+ const h = child.shape.half;
1545
+ volume += 8 * h.x * h.y * h.z;
1546
+ const corners = [
1547
+ { x: -h.x, y: -h.y, z: -h.z },
1548
+ { x: h.x, y: -h.y, z: -h.z },
1549
+ { x: -h.x, y: h.y, z: -h.z },
1550
+ { x: h.x, y: h.y, z: -h.z },
1551
+ { x: -h.x, y: -h.y, z: h.z },
1552
+ { x: h.x, y: -h.y, z: h.z },
1553
+ { x: -h.x, y: h.y, z: h.z },
1554
+ { x: h.x, y: h.y, z: h.z },
1555
+ ];
1556
+ points.push(...corners.map((c) => addVec3(childCenter, rotatePointByQuat(childQ, c))));
1557
+ }
1558
+ else if (child.shape.kind === 'sphere') {
1559
+ const r = child.shape.radius;
1560
+ volume += (4 / 3) * Math.PI * r * r * r;
1561
+ points.push(childCenter, { x: childCenter.x - r, y: childCenter.y, z: childCenter.z }, { x: childCenter.x + r, y: childCenter.y, z: childCenter.z }, { x: childCenter.x, y: childCenter.y - r, z: childCenter.z }, { x: childCenter.x, y: childCenter.y + r, z: childCenter.z }, { x: childCenter.x, y: childCenter.y, z: childCenter.z - r }, { x: childCenter.x, y: childCenter.y, z: childCenter.z + r });
1562
+ }
1563
+ else if (child.shape.kind === 'capsule') {
1564
+ const r = child.shape.radius;
1565
+ const hh = child.shape.halfHeight;
1566
+ volume += (4 / 3) * Math.PI * r * r * r + 2 * Math.PI * r * r * hh;
1567
+ const capTop = addVec3(childCenter, rotatePointByQuat(childQ, { x: 0, y: hh, z: 0 }));
1568
+ const capBot = addVec3(childCenter, rotatePointByQuat(childQ, { x: 0, y: -hh, z: 0 }));
1569
+ points.push(capTop, capBot, { x: capTop.x - r, y: capTop.y, z: capTop.z }, { x: capTop.x + r, y: capTop.y, z: capTop.z }, { x: capTop.x, y: capTop.y - r, z: capTop.z }, { x: capTop.x, y: capTop.y + r, z: capTop.z }, { x: capTop.x, y: capTop.y, z: capTop.z - r }, { x: capTop.x, y: capTop.y, z: capTop.z + r }, { x: capBot.x - r, y: capBot.y, z: capBot.z }, { x: capBot.x + r, y: capBot.y, z: capBot.z }, { x: capBot.x, y: capBot.y - r, z: capBot.z }, { x: capBot.x, y: capBot.y + r, z: capBot.z }, { x: capBot.x, y: capBot.y, z: capBot.z - r }, { x: capBot.x, y: capBot.y, z: capBot.z + r });
1570
+ }
1571
+ else if (child.shape.kind === 'hull') {
1572
+ const verts = child.shape.data.vertices;
1573
+ const hullPoints = verts.map((v) => addVec3(childCenter, rotatePointByQuat(childQ, v)));
1574
+ points.push(...hullPoints);
1575
+ if (hullPoints.length > 0) {
1576
+ let hxMin = hullPoints[0].x;
1577
+ let hxMax = hullPoints[0].x;
1578
+ let hyMin = hullPoints[0].y;
1579
+ let hyMax = hullPoints[0].y;
1580
+ let hzMin = hullPoints[0].z;
1581
+ let hzMax = hullPoints[0].z;
1582
+ for (let i = 1; i < hullPoints.length; i += 1) {
1583
+ const p = hullPoints[i];
1584
+ if (p.x < hxMin)
1585
+ hxMin = p.x;
1586
+ if (p.x > hxMax)
1587
+ hxMax = p.x;
1588
+ if (p.y < hyMin)
1589
+ hyMin = p.y;
1590
+ if (p.y > hyMax)
1591
+ hyMax = p.y;
1592
+ if (p.z < hzMin)
1593
+ hzMin = p.z;
1594
+ if (p.z > hzMax)
1595
+ hzMax = p.z;
1596
+ }
1597
+ volume += Math.max(0.001, (hxMax - hxMin) * (hyMax - hyMin) * (hzMax - hzMin) * 0.5);
1598
+ }
1599
+ }
1600
+ }
1601
+ if (points.length > 0) {
1602
+ xMin = points[0].x;
1603
+ xMax = points[0].x;
1604
+ yMin = points[0].y;
1605
+ yMax = points[0].y;
1606
+ zMin = points[0].z;
1607
+ zMax = points[0].z;
1608
+ for (let i = 1; i < points.length; i += 1) {
1609
+ const p = points[i];
1610
+ if (p.x < xMin)
1611
+ xMin = p.x;
1612
+ if (p.x > xMax)
1613
+ xMax = p.x;
1614
+ if (p.y < yMin)
1615
+ yMin = p.y;
1616
+ if (p.y > yMax)
1617
+ yMax = p.y;
1618
+ if (p.z < zMin)
1619
+ zMin = p.z;
1620
+ if (p.z > zMax)
1621
+ zMax = p.z;
1622
+ }
1623
+ }
1624
+ }
1625
+ if (xMax < bounds.minX || xMin > bounds.maxX || zMax < bounds.minZ || zMin > bounds.maxZ) {
1626
+ return undefined;
1627
+ }
1628
+ if (yMax <= bounds.minY || yMin >= bounds.maxY) {
1629
+ return undefined;
1630
+ }
1631
+ const yBottom = Math.max(bounds.minY, yMin);
1632
+ const yTop = Math.min(bounds.maxY, yMax);
1633
+ const span = Math.max(1e-6, yMax - yMin);
1634
+ const fraction = Math.min(1.0, Math.max(0.0, (yTop - yBottom) / span));
1635
+ let cbX = b.c.x;
1636
+ let cbY = (yBottom + yTop) / 2;
1637
+ let cbZ = b.c.z;
1638
+ if (fraction >= 1.0) {
1639
+ // Fully submerged: center of buoyancy coincides with body centroid (no phantom righting torque)
1640
+ cbX = b.c.x;
1641
+ cbY = b.c.y;
1642
+ cbZ = b.c.z;
1643
+ }
1644
+ else {
1645
+ // Partially submerged: depth-weighted average of submerged sample points relative to fluid surface
1646
+ let sumWeight = 0;
1647
+ let sumX = 0;
1648
+ let sumZ = 0;
1649
+ const surfaceY = bounds.maxY;
1650
+ for (const p of points) {
1651
+ if (p.y < surfaceY) {
1652
+ const depth = surfaceY - p.y;
1653
+ sumWeight += depth;
1654
+ sumX += p.x * depth;
1655
+ sumZ += p.z * depth;
1656
+ }
1657
+ }
1658
+ if (sumWeight > 1e-9) {
1659
+ cbX = sumX / sumWeight;
1660
+ cbZ = sumZ / sumWeight;
1661
+ }
1662
+ }
1663
+ return {
1664
+ fraction,
1665
+ centerOfBuoyancy: { x: cbX, y: cbY, z: cbZ },
1666
+ displacedVolume: volume,
1667
+ };
1668
+ }
1669
+ function applyFluidForces(bodies, invInertia, fluids, gravityY, dt) {
1670
+ for (let index = 0; index < bodies.length; index += 1) {
1671
+ const b = bodies[index];
1672
+ if (!solid(b) || !b.isDynamic) {
1673
+ continue;
1674
+ }
1675
+ for (const fluid of fluids) {
1676
+ if (fluid.layer !== undefined && fluid.mask !== undefined) {
1677
+ if ((fluid.mask & b.layer) === 0 || (b.mask & fluid.layer) === 0) {
1678
+ continue;
1679
+ }
1680
+ }
1681
+ const submergence = computeSubmergence(b, fluid.bounds);
1682
+ if (submergence === undefined || submergence.fraction <= 0) {
1683
+ continue;
1684
+ }
1685
+ const { fraction, centerOfBuoyancy, displacedVolume } = submergence;
1686
+ const fluidDensity = fluid.density ?? 1.0;
1687
+ const linearDragCoeff = fluid.linearDrag ?? 1.0;
1688
+ const angularDragCoeff = fluid.angularDrag ?? 0.5;
1689
+ const flowVel = fluid.flowVelocity ?? ZERO;
1690
+ const effG = gravityY * b.gravityScale;
1691
+ if (Math.abs(effG) > 0 && b.invMassVec.y > 0) {
1692
+ // Buoyant force opposes effective gravity
1693
+ const buoyantForceY = -effG * fluidDensity * displacedVolume * fraction;
1694
+ const buoyantAccelY = buoyantForceY * b.invMassVec.y;
1695
+ b.v.y += buoyantAccelY * dt;
1696
+ const rB = subVec3(centerOfBuoyancy, b.c);
1697
+ const torque = {
1698
+ x: -rB.z * buoyantForceY,
1699
+ y: 0,
1700
+ z: rB.x * buoyantForceY,
1701
+ };
1702
+ const angImpulse = scaleVec3(torque, dt);
1703
+ let dw = mulMat3Vec(invInertia[index], angImpulse);
1704
+ const dwLen = solverSqrt(dotVec3(dw, dw));
1705
+ if (dwLen > 0.5) {
1706
+ dw = scaleVec3(dw, 0.5 / dwLen);
1707
+ }
1708
+ b.w = addVec3(b.w, dw);
1709
+ }
1710
+ const vRel = subVec3(b.v, flowVel);
1711
+ const linDragFactor = 1 / (1 + linearDragCoeff * fraction * dt);
1712
+ b.v = addVec3(scaleVec3(vRel, linDragFactor), flowVel);
1713
+ const angDragFactor = 1 / (1 + angularDragCoeff * fraction * dt);
1714
+ b.w = scaleVec3(b.w, angDragFactor);
1715
+ }
1716
+ }
1717
+ }
1438
1718
  /** Runs one deterministic rigid-body step in place over `bodies`. */
1439
1719
  export function solveRigidStep(bodies, options) {
1440
1720
  /* istanbul ignore next -- worlds are validated far below this bound; guards the packed warm-cache key from silent index collisions. */
@@ -1589,6 +1869,25 @@ export function solveRigidStep(bodies, options) {
1589
1869
  b.lowVelFrames = 0;
1590
1870
  }
1591
1871
  }
1872
+ if (options.fluids && options.fluids.length > 0) {
1873
+ for (const b of bodies) {
1874
+ if (b.isDynamic && b.sleeping) {
1875
+ for (const f of options.fluids) {
1876
+ if (f.layer !== undefined && f.mask !== undefined) {
1877
+ if ((f.mask & b.layer) === 0 || (b.mask & f.layer) === 0) {
1878
+ continue;
1879
+ }
1880
+ }
1881
+ const sub = computeSubmergence(b, f.bounds);
1882
+ if (sub && sub.fraction > 0) {
1883
+ b.sleeping = false;
1884
+ b.lowVelFrames = 0;
1885
+ break;
1886
+ }
1887
+ }
1888
+ }
1889
+ }
1890
+ }
1592
1891
  for (const m of manifolds) {
1593
1892
  wakeIfContacted(bodies[m.a], bodies[m.b]);
1594
1893
  wakeIfContacted(bodies[m.b], bodies[m.a]);
@@ -1685,6 +1984,13 @@ export function solveRigidStep(bodies, options) {
1685
1984
  B.w = addVec3(B.w, mulMat3Vec(invInertia[b], angularImpulse));
1686
1985
  }
1687
1986
  },
1987
+ applyAngularToBody: (index, angularImpulse) => {
1988
+ const body = bodies[index];
1989
+ if (!solid(body)) {
1990
+ return;
1991
+ }
1992
+ body.w = addVec3(body.w, mulMat3Vec(invInertia[index], angularImpulse));
1993
+ },
1688
1994
  applyToBody: (index, r, impulse) => {
1689
1995
  const body = bodies[index];
1690
1996
  if (!solid(body)) {
@@ -1706,6 +2012,9 @@ export function solveRigidStep(bodies, options) {
1706
2012
  b.v.y = b.v.y + options.gravityY * b.gravityScale * h;
1707
2013
  }
1708
2014
  }
2015
+ if (options.fluids && options.fluids.length > 0) {
2016
+ applyFluidForces(bodies, invInertia, options.fluids, options.gravityY, h);
2017
+ }
1709
2018
  for (const joint of joints) {
1710
2019
  joint.prepare(jointCtx);
1711
2020
  }
@@ -116,3 +116,16 @@ export interface SplitDeterministicVoxelChunkOptions3D {
116
116
  * occupied count, and whether any of its voxels touch the specified anchor faces.
117
117
  */
118
118
  export declare function splitDeterministicVoxelChunk3D(chunk: DeterministicVoxelChunk3D, options?: SplitDeterministicVoxelChunkOptions3D): readonly VoxelSubChunk3D[];
119
+ export interface DeterministicVoxelCompoundSplitResult3D {
120
+ readonly compounds: readonly (DeterministicVoxelCompoundShape3D | undefined)[];
121
+ readonly preservedBoxesCount: number;
122
+ readonly recookedBoxesCount: number;
123
+ }
124
+ /**
125
+ * Incrementally slices a parent compound collider across partitioned sub-chunks
126
+ * from {@link splitDeterministicVoxelChunk3D}.
127
+ * Preserves intact child box colliders that fall entirely within a sub-chunk's volume
128
+ * without re-cooking, rebasing their offset coordinates to sub-chunk local space.
129
+ * Only re-cooks severed/split boundary voxels that were not covered by preserved boxes.
130
+ */
131
+ export declare function splitDeterministicVoxelCompoundCollider3D(parentCompound: DeterministicVoxelCompoundShape3D, subChunks: readonly VoxelSubChunk3D[]): DeterministicVoxelCompoundSplitResult3D;
@@ -693,3 +693,127 @@ export function splitDeterministicVoxelChunk3D(chunk, options) {
693
693
  });
694
694
  return subChunks;
695
695
  }
696
+ /**
697
+ * Incrementally slices a parent compound collider across partitioned sub-chunks
698
+ * from {@link splitDeterministicVoxelChunk3D}.
699
+ * Preserves intact child box colliders that fall entirely within a sub-chunk's volume
700
+ * without re-cooking, rebasing their offset coordinates to sub-chunk local space.
701
+ * Only re-cooks severed/split boundary voxels that were not covered by preserved boxes.
702
+ */
703
+ export function splitDeterministicVoxelCompoundCollider3D(parentCompound, subChunks) {
704
+ if (subChunks.length === 0) {
705
+ return {
706
+ compounds: [],
707
+ preservedBoxesCount: 0,
708
+ recookedBoxesCount: 0,
709
+ };
710
+ }
711
+ let totalPreservedBoxes = 0;
712
+ let totalRecookedBoxes = 0;
713
+ const compounds = [];
714
+ for (const subChunk of subChunks) {
715
+ const chunk = subChunk.chunk;
716
+ const cellSize = chunk.cellSize;
717
+ const scX = subChunk.offsetCells.x;
718
+ const scY = subChunk.offsetCells.y;
719
+ const scZ = subChunk.offsetCells.z;
720
+ const maxBoundX = scX + chunk.dimX - 1;
721
+ const maxBoundY = scY + chunk.dimY - 1;
722
+ const maxBoundZ = scZ + chunk.dimZ - 1;
723
+ const totalCells = chunk.dimX * chunk.dimY * chunk.dimZ;
724
+ const coveredInSubChunk = new Uint8Array(totalCells);
725
+ const preservedChildren = [];
726
+ for (const child of parentCompound.children) {
727
+ if (child.shape.type !== 'box') {
728
+ continue;
729
+ }
730
+ const boxMinX = Math.round((child.offset.x - child.shape.halfX) / cellSize + 0.5);
731
+ const boxMaxX = Math.round((child.offset.x + child.shape.halfX) / cellSize - 0.5);
732
+ const boxMinY = Math.round((child.offset.y - child.shape.halfY) / cellSize + 0.5);
733
+ const boxMaxY = Math.round((child.offset.y + child.shape.halfY) / cellSize - 0.5);
734
+ const boxMinZ = Math.round((child.offset.z - child.shape.halfZ) / cellSize + 0.5);
735
+ const boxMaxZ = Math.round((child.offset.z + child.shape.halfZ) / cellSize - 0.5);
736
+ if (boxMinX >= scX && boxMaxX <= maxBoundX &&
737
+ boxMinY >= scY && boxMaxY <= maxBoundY &&
738
+ boxMinZ >= scZ && boxMaxZ <= maxBoundZ) {
739
+ let isIntact = true;
740
+ for (let bz = boxMinZ; bz <= boxMaxZ && isIntact; bz++) {
741
+ for (let by = boxMinY; by <= boxMaxY && isIntact; by++) {
742
+ for (let bx = boxMinX; bx <= boxMaxX; bx++) {
743
+ const lx = bx - scX;
744
+ const ly = by - scY;
745
+ const lz = bz - scZ;
746
+ const idx = lx + chunk.dimX * (ly + chunk.dimY * lz);
747
+ if (chunk.occupancy[idx] === 0) {
748
+ isIntact = false;
749
+ break;
750
+ }
751
+ }
752
+ }
753
+ }
754
+ if (isIntact) {
755
+ for (let bz = boxMinZ; bz <= boxMaxZ; bz++) {
756
+ for (let by = boxMinY; by <= boxMaxY; by++) {
757
+ for (let bx = boxMinX; bx <= boxMaxX; bx++) {
758
+ const lx = bx - scX;
759
+ const ly = by - scY;
760
+ const lz = bz - scZ;
761
+ const idx = lx + chunk.dimX * (ly + chunk.dimY * lz);
762
+ coveredInSubChunk[idx] = 1;
763
+ }
764
+ }
765
+ }
766
+ preservedChildren.push({
767
+ shape: child.shape,
768
+ offset: {
769
+ x: child.offset.x - scX * cellSize,
770
+ y: child.offset.y - scY * cellSize,
771
+ z: child.offset.z - scZ * cellSize,
772
+ },
773
+ ...(child.quarterTurns !== undefined ? { quarterTurns: child.quarterTurns } : {}),
774
+ });
775
+ totalPreservedBoxes += 1;
776
+ }
777
+ }
778
+ }
779
+ let uncoveredCount = 0;
780
+ for (let i = 0; i < totalCells; i++) {
781
+ if (chunk.occupancy[i] !== 0 && coveredInSubChunk[i] === 0) {
782
+ uncoveredCount += 1;
783
+ }
784
+ }
785
+ let recookedChildren = [];
786
+ if (uncoveredCount > 0) {
787
+ const uncoveredOccupancy = new Uint8Array(totalCells);
788
+ for (let i = 0; i < totalCells; i++) {
789
+ if (chunk.occupancy[i] !== 0 && coveredInSubChunk[i] === 0) {
790
+ uncoveredOccupancy[i] = 1;
791
+ }
792
+ }
793
+ const uncoveredChunk = {
794
+ dimX: chunk.dimX,
795
+ dimY: chunk.dimY,
796
+ dimZ: chunk.dimZ,
797
+ cellSize: chunk.cellSize,
798
+ occupancy: uncoveredOccupancy,
799
+ };
800
+ recookedChildren = cookSubregionBoxes(uncoveredChunk, 0, 0, 0, chunk.dimX - 1, chunk.dimY - 1, chunk.dimZ - 1);
801
+ totalRecookedBoxes += recookedChildren.length;
802
+ }
803
+ const finalChildren = [...preservedChildren, ...recookedChildren];
804
+ if (finalChildren.length === 0) {
805
+ compounds.push(undefined);
806
+ }
807
+ else {
808
+ compounds.push({
809
+ type: 'compound',
810
+ children: finalChildren,
811
+ });
812
+ }
813
+ }
814
+ return {
815
+ compounds,
816
+ preservedBoxesCount: totalPreservedBoxes,
817
+ recookedBoxesCount: totalRecookedBoxes,
818
+ };
819
+ }
@@ -1,5 +1,7 @@
1
1
  import { type DeterministicPhysicsMaterial } from './deterministic-primitives.js';
2
2
  import { type Point3D } from './physics3d-shared.js';
3
+ import { type DeterministicPhysicsAabb3D, type DeterministicFluidVolume3D } from './physics3d-solver.js';
4
+ export type { DeterministicPhysicsAabb3D, DeterministicFluidVolume3D };
3
5
  import { type DeterministicPhysicsJoint3D } from './physics3d-joints.js';
4
6
  export type DeterministicPhysicsPrimitiveShape3D = {
5
7
  readonly type: 'box';
@@ -364,6 +366,8 @@ export interface DeterministicPhysicsStepResult3D {
364
366
  readonly contacts: readonly DeterministicPhysicsContactEvent3D[];
365
367
  /** Joint ids that broke this step (newly, sorted); sticky once broken. */
366
368
  readonly brokenJointIds: readonly string[];
369
+ /** Alias for brokenJointIds requested for ergonomics parity. */
370
+ readonly brokenJoints: readonly string[];
367
371
  readonly scheduledQueryResults: readonly {
368
372
  readonly id: string;
369
373
  readonly hits: readonly DeterministicPhysicsHit3D[];
@@ -480,6 +484,7 @@ export interface DeterministicPhysicsStepOptions3D {
480
484
  readonly computeChecksum?: boolean;
481
485
  readonly computeBroadphasePairs?: boolean;
482
486
  readonly pairs?: readonly (readonly [number, number])[];
487
+ readonly fluids?: readonly DeterministicFluidVolume3D[];
483
488
  }
484
489
  export declare function stepDeterministicPhysicsWorld3D(world: DeterministicPhysicsWorld3D, options?: DeterministicPhysicsStepOptions3D): DeterministicPhysicsStepResult3D;
485
490
  /** O(1) body lookup backed by the per-world query-index cache. */
package/dist/physics3d.js CHANGED
@@ -693,7 +693,52 @@ export function setDeterministicBodyVelocity3D(world, bodyId, velocity, angularV
693
693
  });
694
694
  return { ...world, bodies: normalizeBodies(bodies, 'trusted') };
695
695
  }
696
+ function validateFluidVolume(fluid) {
697
+ if (typeof fluid.id !== 'string' || fluid.id.length === 0) {
698
+ throw new Error('physics3d fluid: id must be a non-empty string');
699
+ }
700
+ const bounds = fluid.bounds;
701
+ if (!bounds || typeof bounds !== 'object') {
702
+ throw new Error(`physics3d fluid ${fluid.id}: bounds must be an object`);
703
+ }
704
+ const { minX, minY, minZ, maxX, maxY, maxZ } = bounds;
705
+ if (!Number.isFinite(minX) ||
706
+ !Number.isFinite(minY) ||
707
+ !Number.isFinite(minZ) ||
708
+ !Number.isFinite(maxX) ||
709
+ !Number.isFinite(maxY) ||
710
+ !Number.isFinite(maxZ)) {
711
+ throw new Error(`physics3d fluid ${fluid.id}: bounds must contain finite numbers`);
712
+ }
713
+ if (minX > maxX) {
714
+ throw new Error(`physics3d fluid ${fluid.id}: minX must not exceed maxX`);
715
+ }
716
+ if (minY > maxY) {
717
+ throw new Error(`physics3d fluid ${fluid.id}: minY must not exceed maxY`);
718
+ }
719
+ if (minZ > maxZ) {
720
+ throw new Error(`physics3d fluid ${fluid.id}: minZ must not exceed maxZ`);
721
+ }
722
+ if (fluid.density !== undefined && (!Number.isFinite(fluid.density) || fluid.density <= 0)) {
723
+ throw new Error(`physics3d fluid ${fluid.id}: density must be positive`);
724
+ }
725
+ if (fluid.linearDrag !== undefined && (!Number.isFinite(fluid.linearDrag) || fluid.linearDrag < 0)) {
726
+ throw new Error(`physics3d fluid ${fluid.id}: linearDrag must be non-negative`);
727
+ }
728
+ if (fluid.angularDrag !== undefined && (!Number.isFinite(fluid.angularDrag) || fluid.angularDrag < 0)) {
729
+ throw new Error(`physics3d fluid ${fluid.id}: angularDrag must be non-negative`);
730
+ }
731
+ if (fluid.flowVelocity !== undefined) {
732
+ const { x, y, z } = fluid.flowVelocity;
733
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
734
+ throw new Error(`physics3d fluid ${fluid.id}: flowVelocity must be finite`);
735
+ }
736
+ }
737
+ }
696
738
  export function stepDeterministicPhysicsWorld3D(world, options = {}) {
739
+ for (const fluid of options.fluids ?? []) {
740
+ validateFluidVolume(fluid);
741
+ }
697
742
  const scheduledQueries = sortScheduledQueries(options.scheduledQueries ?? []);
698
743
  const prePhysicsScheduledQueryResults = scheduledQueries
699
744
  .filter((query) => query.phase === 'pre-physics')
@@ -711,7 +756,7 @@ export function stepDeterministicPhysicsWorld3D(world, options = {}) {
711
756
  }
712
757
  const dt = options.dtTicks ?? 1;
713
758
  const gravityY = options.gravityY ?? 0;
714
- const rigid = runRigidSolver(world, dt, gravityY, velocityIterations, substeps, options.joints ?? [], options.pairs);
759
+ const rigid = runRigidSolver(world, dt, gravityY, velocityIterations, substeps, options.joints ?? [], options.pairs, options.fluids);
715
760
  const constrained = solveConstraints3D(rigid.bodies, options.constraints ?? []);
716
761
  const solvedBodies = constrained.bodies;
717
762
  // Dynamic-involved solid-collision events come from the solver's manifolds (so
@@ -783,6 +828,7 @@ export function stepDeterministicPhysicsWorld3D(world, options = {}) {
783
828
  world: nextWorld,
784
829
  contacts: events,
785
830
  brokenJointIds: rigid.brokenJointIds,
831
+ brokenJoints: rigid.brokenJointIds,
786
832
  scheduledQueryResults,
787
833
  prePhysicsScheduledQueryResults,
788
834
  postPhysicsScheduledQueryResults,
@@ -2236,7 +2282,7 @@ function sleepingRebuildIsNoop(body, sb) {
2236
2282
  && eq(q(sb.w.x), body.angularVel.x) && eq(q(sb.w.y), body.angularVel.y) && eq(q(sb.w.z), body.angularVel.z)
2237
2283
  && eq(sb.lowVelFrames, body.lowVelFrames);
2238
2284
  }
2239
- function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joints, pairs) {
2285
+ function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joints, pairs, fluids) {
2240
2286
  const source = world.bodies;
2241
2287
  const indexById = new Map(source.map((body, index) => [body.id, index]));
2242
2288
  const solverBodies = source.map((body, index) => buildSolverBody(body, index));
@@ -2281,6 +2327,7 @@ function runRigidSolver(world, dt, gravityY, velocityIterations, substeps, joint
2281
2327
  warm,
2282
2328
  pairs,
2283
2329
  joints: solverJoints,
2330
+ fluids,
2284
2331
  });
2285
2332
  // Post-solve safety clamp: no fast body may end past its TOI along its motion.
2286
2333
  for (const [index, clamp] of fastClamps) {
package/dist/runner.d.ts CHANGED
@@ -85,7 +85,17 @@ export interface SyncplayRunnerOfflineOptions<Input> {
85
85
  }
86
86
  export interface SyncplayRunnerSnapshot<RenderState> {
87
87
  readonly status: SyncplayRunnerStatus;
88
+ /**
89
+ * Discrete tick render state. Note: to prevent 30Hz display judder on
90
+ * high-refresh displays, prefer interpolatedRenderState or runner.getRenderState().
91
+ */
88
92
  readonly renderState?: Readonly<RenderState>;
93
+ /**
94
+ * Smooth presentation state interpolated between previous and current
95
+ * render states at the current `renderAlpha`. If no interpolator is
96
+ * configured or interpolation throws, falls back to `renderState`.
97
+ */
98
+ readonly interpolatedRenderState?: Readonly<RenderState>;
89
99
  readonly renderAlpha: number;
90
100
  readonly localSlot: number;
91
101
  readonly rollbackCount: number;
package/dist/runner.js CHANGED
@@ -68,9 +68,28 @@ export function createSyncplayRunner(config) {
68
68
  return localSession !== undefined ? localSession.currentFrame : 0;
69
69
  }
70
70
  function snapshot() {
71
+ let interpolatedRender;
72
+ if (currentRender !== undefined) {
73
+ if (previousRender !== undefined && config.presentation.interpolate !== undefined) {
74
+ try {
75
+ interpolatedRender = config.presentation.interpolate(previousRender, currentRender, renderAlpha);
76
+ }
77
+ catch {
78
+ interpolatedRender = currentRender;
79
+ }
80
+ }
81
+ else {
82
+ interpolatedRender = currentRender;
83
+ }
84
+ }
71
85
  return Object.freeze({
72
86
  status,
73
- ...(currentRender === undefined ? {} : { renderState: currentRender }),
87
+ ...(currentRender === undefined
88
+ ? {}
89
+ : {
90
+ renderState: currentRender,
91
+ interpolatedRenderState: interpolatedRender,
92
+ }),
74
93
  renderAlpha,
75
94
  localSlot,
76
95
  rollbackCount: networkClient?.rollbacks ?? 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-inc/rundot-syncplay",
3
- "version": "5.28.0-beta.2",
3
+ "version": "5.28.0-beta.4",
4
4
  "description": "Kinetix orchestration, rollback, replay, late-join, and deterministic networking for RUN.game",
5
5
  "repository": {
6
6
  "type": "git",
@@ -168,7 +168,7 @@
168
168
  "test:core": "node --expose-gc --import tsx --test --test-concurrency=1 tests/canonical.test.ts tests/canonical-portability.test.ts tests/runtime-session.test.ts tests/kinetix-runtime-session.test.ts tests/runtime-support.test.ts tests/runtime-guards.test.ts tests/r3f-render-adapter.test.ts tests/snapshot-cost.test.ts tests/sdkSession.test.ts tests/input-authority.test.ts tests/session-wire.test.ts tests/runtime-room-metadata.test.ts tests/session-snapshot.test.ts tests/authority-room.test.ts tests/networked-client-snapshot.test.ts tests/runner.test.ts tests/testing.test.ts tests/browser-session.test.ts tests/secret-config.test.ts tests/secret-protocol.test.ts tests/secret-authority.test.ts tests/secret-client.test.ts tests/late-join-checksum.test.ts tests/late-join-input-backfill.test.ts tests/replay-scoring.test.ts tests/bot-backfill.test.ts tests/latency-simulation.test.ts tests/production-input-budget.test.ts tests/time-sync.test.ts tests/match-log.test.ts tests/spectator.test.ts tests/synctest.test.ts tests/instant-replay.test.ts tests/session-promotion.test.ts tests/physics3d-broadphase-stats.test.ts tests/entrypoints.test.ts tests/export-surface.test.ts tests/module-seam.test.ts tests/module-contract-portability.test.ts tests/module-identity.test.ts tests/module-corpus.test.ts tests/component-model.test.ts tests/component-model-rollback.test.ts tests/component-model-integration.test.ts tests/actors-ai.test.ts tests/character-3d.test.ts tests/character-3d-certification.test.ts tests/combat-modules.test.ts tests/combat-modules-integration.test.ts tests/firearm.test.ts tests/gameplay-effects.test.ts tests/spatial-interaction.test.ts tests/stats-abilities.test.ts tests/inventory-equipment.test.ts tests/inventory-equipment-certification.test.ts tests/platformer-2d.test.ts tests/platformer-2d-certification.test.ts tests/module-promotion.test.ts tests/module-docs.test.ts tests/module-bundle-budget.test.ts tests/published-side-effects.test.ts tests/host-timing.test.ts tests/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/command-ingress.test.ts tests/session-reconfigure.test.ts tests/prediction-mode.test.ts tests/ephemeral-channel.test.ts tests/runner-surface.test.ts tests/runner-harness.test.ts tests/effects-adapter-bounds.test.ts tests/simulated-match-secrets.test.ts tests/run-glue-surface.test.mjs core/tests/core-boundary.test.mjs tests/math-core-parity.test.ts tests/math-profile.test.ts tests/movement3d-kcc-unit-contract.test.ts tests/movement3d-static-ground.test.ts",
169
169
  "test:sdk-contract": "node --import tsx --test tests/sdk-runtime-contract.test.ts",
170
170
  "test:soak": "tsx --test tests/runtime-soak.test.ts",
171
- "test:extended": "tsx --test tests/kcc-vehicle-characterization.test.ts tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-debug-wireframe.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-forces-and-contacts.test.ts tests/physics3d-geometry-limits.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-kinematic-poses.test.ts tests/physics3d-kinematic-transforms.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-queries-capsule-childindex.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shape-builders.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-static-memoization.test.ts tests/physics3d-static-overlap-memo.test.ts tests/physics3d-cylinder-hull.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel-carving.test.ts tests/physics3d-voxel-dda.test.ts tests/physics3d-voxel-split.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
171
+ "test:extended": "tsx --test tests/kcc-vehicle-characterization.test.ts tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-buoyancy.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-debug-wireframe.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-forces-and-contacts.test.ts tests/physics3d-geometry-limits.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-kinematic-poses.test.ts tests/physics3d-kinematic-transforms.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-queries-capsule-childindex.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shape-builders.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-static-memoization.test.ts tests/physics3d-static-overlap-memo.test.ts tests/physics3d-cylinder-hull.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel-carving.test.ts tests/physics3d-voxel-dda.test.ts tests/physics3d-voxel-split.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
172
172
  "test:ci": "npm run build && npm run test",
173
173
  "build:native": "node core/scripts/build-native.mjs",
174
174
  "build:mobile-native": "node core/scripts/build-mobile-native.mjs",