@series-inc/rundot-syncplay 5.26.0-beta.2 → 5.26.0-beta.3
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 +17 -0
- package/core/native/deterministic_math.hpp +4 -3
- package/core/native/fixed_math.cpp +741 -0
- package/core/native/fixed_math.hpp +111 -0
- package/core/native/kinetix-native-main.cpp +227 -88
- package/core/native/mobile/box3d_probe_scenario.cpp +101 -0
- package/core/native/mobile/box3d_probe_scenario.hpp +25 -0
- package/core/native/mobile/syncplay_native_probe.cpp +83 -0
- package/core/native/mobile/syncplay_native_probe_abi.h +41 -0
- package/core/physics.d.ts +48 -0
- package/core/scripts/build-mobile-native.mjs +99 -0
- package/core/scripts/build-native.mjs +15 -1
- package/core/scripts/build-wasm-fixed-math.mjs +58 -0
- package/core/scripts/run-box3d-benchmark.mjs +22 -124
- package/core/src/physics/box3d-probe.mjs +171 -0
- package/core/src/physics/index.mjs +156 -19
- package/core/src/physics/physics-wasm-module.generated.mjs +3 -3
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +1 -0
- package/dist/creator.d.ts +1 -0
- package/dist/creator.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/movement3d.d.ts +9 -0
- package/dist/movement3d.js +185 -24
- package/dist/physics3d-heightfield.d.ts +10 -0
- package/dist/physics3d-heightfield.js +33 -0
- package/dist/physics3d-hull.d.ts +14 -0
- package/dist/physics3d-hull.js +45 -0
- package/dist/physics3d.js +28 -1
- package/package.json +8 -4
- package/dist/prediction-culling.d.ts +0 -34
- package/dist/prediction-culling.js +0 -200
package/dist/browser.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ 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 { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
29
29
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
30
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
30
31
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
31
32
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
32
33
|
export { createDeterministicR3fRenderAdapter as createDeterministicR3fAdapter, createDeterministicR3fRenderAdapter, interpolateDeterministicRenderNumber, interpolateDeterministicRenderVec2 } from './r3f-render-adapter.js';
|
package/dist/browser.js
CHANGED
|
@@ -28,6 +28,7 @@ export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js
|
|
|
28
28
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
29
29
|
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
30
30
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
31
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
31
32
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
32
33
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
33
34
|
export { createDeterministicR3fRenderAdapter as createDeterministicR3fAdapter, createDeterministicR3fRenderAdapter, interpolateDeterministicRenderNumber, interpolateDeterministicRenderVec2 } from './r3f-render-adapter.js';
|
package/dist/creator.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ 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 { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
|
|
16
16
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
17
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
17
18
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
|
18
19
|
export type { DeterministicContactCacheEntry3D, DeterministicJointCacheEntry3D, DeterministicPhysicsActivePair3D, DeterministicPhysicsAxisLock3D, DeterministicPhysicsBody3D, DeterministicPhysicsCallbackMask3D, DeterministicPhysicsCompoundChild3D, DeterministicPhysicsCompoundChildShape3D, DeterministicPhysicsConstraint3D, DeterministicPhysicsContactDetail3D, DeterministicPhysicsContactEvent3D, DeterministicPhysicsDetailedHit3D, DeterministicPhysicsHit3D, DeterministicPhysicsHullShape3D, DeterministicPhysicsImpulseApplication3D, DeterministicPhysicsOverlapQuery3D, DeterministicPhysicsPrimitiveShape3D, DeterministicPhysicsQuaternion3D, DeterministicPhysicsQueryOptions3D, DeterministicPhysicsRaycastQuery3D, DeterministicPhysicsScheduledQuery3D, DeterministicPhysicsShape3D, DeterministicPhysicsShapeCastQuery3D, DeterministicPhysicsStepResult3D, DeterministicPhysicsVec3, DeterministicPhysicsWorld3D, DeterministicPhysicsWorldEdit3D, } from './physics3d.js';
|
|
19
20
|
export type { DeterministicVoxelChunk3D, DeterministicVoxelCompoundShape3D } from './physics3d-voxel.js';
|
package/dist/creator.js
CHANGED
|
@@ -11,4 +11,5 @@ export { runSyncplaySynctest } from './synctest.js';
|
|
|
11
11
|
// stay off it — the first two are not requested, the third reads wall clock.
|
|
12
12
|
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D, } from './physics3d.js';
|
|
13
13
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
14
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
14
15
|
export { stepDeterministicVehicle3D } from './physics3d-vehicle.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ 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 { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
32
32
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
33
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
33
34
|
export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
|
|
34
35
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
35
36
|
export { runDeterministicKickRecovery3DScenario, runDeterministicMassRatio3DScenario, runDeterministicPyramid3DScenario, runDeterministicStackingSuite3D, runDeterministicTower3DScenario } from './physics3d-stacking.js';
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { fbm2D, hash2, hashUint32, valueNoise1D, valueNoise2D } from './noise.js
|
|
|
31
31
|
export { circleCastDeterministicPhysics2D, createDeterministicPhysicsWorld2D, nearestDeterministicPhysicsHit2D, queryDeterministicPhysicsAabb2D, queryDeterministicPhysicsShapeOverlap2D, raycastAllDeterministicPhysics2D, raycastDeterministicPhysics2D, stepDeterministicPhysicsWorld2D } from './physics2d.js';
|
|
32
32
|
export { applyDeterministicImpulse3D, applyDeterministicImpulses3D, applyDeterministicWorldEdit3D, contactDetailsDeterministicPhysics3D, createDeterministicPhysicsWorld3D, createTrustedDeterministicPhysicsWorld3D, deriveInverseInertia, deterministicPhysicsBodyById3D, deterministicPhysicsCallback3D, overlapDeterministicPhysics3D, raycastDeterministicPhysics3D, raycastDeterministicPhysics3DDetailed, runDeterministicPhysics3DStress, setDeterministicBodyVelocity3D, shapeCastDeterministicPhysics3D, shapeCastDeterministicPhysics3DDetailed, stepDeterministicPhysicsWorld3D } from './physics3d.js';
|
|
33
33
|
export { cookDeterministicVoxelChunkCollider3D } from './physics3d-voxel.js';
|
|
34
|
+
export { cylinderHullVertices } from './physics3d-hull.js';
|
|
34
35
|
export { runDeterministicPhysics3DDestructionCert } from './physics3d-destruction-cert.js';
|
|
35
36
|
export { createDeterministicCosmeticPhysicsWorld3D, setDeterministicCosmeticPhysicsAuthoritativeBodyProxies3D, stepDeterministicCosmeticPhysicsWorld3D } from './cosmetic-physics3d.js';
|
|
36
37
|
export { runDeterministicKickRecovery3DScenario, runDeterministicMassRatio3DScenario, runDeterministicPyramid3DScenario, runDeterministicStackingSuite3D, runDeterministicTower3DScenario } from './physics3d-stacking.js';
|
package/dist/movement3d.d.ts
CHANGED
|
@@ -189,6 +189,15 @@ export declare function stepDeterministicKcc3D(body: DeterministicKccBody3D, inp
|
|
|
189
189
|
readonly maxPushImpulse?: number;
|
|
190
190
|
/** Enable standing on (and inheriting the velocity of) dynamic physics bodies. */
|
|
191
191
|
readonly standOnDynamicBodies?: boolean;
|
|
192
|
+
/**
|
|
193
|
+
* Ground the capsule on STATIC physics bodies, including heightfields.
|
|
194
|
+
* Off by default: without it the physics world only blocks horizontally and
|
|
195
|
+
* vertical support comes from `world.slopes` / `steps` / platforms / `minY`,
|
|
196
|
+
* so a streaming-terrain game had to rebuild slope geometry to stand on the
|
|
197
|
+
* same terrain it already streams as colliders. Opt-in so shipped games keep
|
|
198
|
+
* bit-identical checksums.
|
|
199
|
+
*/
|
|
200
|
+
readonly groundOnStaticBodies?: boolean;
|
|
192
201
|
}): DeterministicKccStepResult3D;
|
|
193
202
|
export interface DeterministicKccCarryState3D {
|
|
194
203
|
readonly bodyId: string;
|
package/dist/movement3d.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defaultChecksum } from './canonical.js';
|
|
2
2
|
import { createDeterministicPhysicsWorld3D, overlapDeterministicPhysics3D, } from './physics3d.js';
|
|
3
|
+
import { heightfieldLocalSurfaceY } from './physics3d-heightfield.js';
|
|
3
4
|
import { mulMat3Vec, quaternionFromQuarterTurns, quatMultiply, quatToMat3, rotatePointByQuat } from './physics3d-shared.js';
|
|
4
5
|
export function stepDeterministicKcc3D(body, input, world, options) {
|
|
5
6
|
let next = applyProcessors(body, input, options.frame, options.processors ?? []);
|
|
@@ -19,6 +20,7 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
19
20
|
let z = next.z + vz;
|
|
20
21
|
let grounded = false;
|
|
21
22
|
let platformId = next.platformId;
|
|
23
|
+
let platformClaimedThisFrame = false;
|
|
22
24
|
if (externalImpulse.applied) {
|
|
23
25
|
events.push('external-impulse');
|
|
24
26
|
}
|
|
@@ -36,6 +38,7 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
36
38
|
vy = 0;
|
|
37
39
|
grounded = true;
|
|
38
40
|
platformId = platform.id;
|
|
41
|
+
platformClaimedThisFrame = true;
|
|
39
42
|
events.push('moving-platform');
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -75,6 +78,58 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
75
78
|
coyoteFrames = 0;
|
|
76
79
|
events.push(next.grounded ? 'jump' : 'coyote-jump');
|
|
77
80
|
}
|
|
81
|
+
const previousFeetY = next.y - kccFeetOffset(geometry);
|
|
82
|
+
let groundedOnStatic = false;
|
|
83
|
+
let groundedOnDynamic = false;
|
|
84
|
+
// Resolved before the dynamic path so the higher of the two wins: a crate
|
|
85
|
+
// standing on a floor must not lose its platform to the floor underneath it.
|
|
86
|
+
const staticGround = options.physics !== undefined && options.groundOnStaticBodies === true && vy <= 0
|
|
87
|
+
? staticGroundUnderKcc(options.physics, x, y, z, previousFeetY, geometry, new Set(options.ignoredPhysicsBodyIds ?? []), world.stepHeight)
|
|
88
|
+
: undefined;
|
|
89
|
+
if (options.physics !== undefined && options.standOnDynamicBodies === true && vy <= 0) {
|
|
90
|
+
const ground = dynamicGroundUnderKcc(options.physics, x, y, z, geometry, new Set(options.ignoredPhysicsBodyIds ?? []));
|
|
91
|
+
if (ground !== undefined && (staticGround === undefined || ground.topY >= staticGround.topY)) {
|
|
92
|
+
groundedOnDynamic = true;
|
|
93
|
+
platformClaimedThisFrame = true;
|
|
94
|
+
y = ground.topY;
|
|
95
|
+
vy = 0;
|
|
96
|
+
grounded = true;
|
|
97
|
+
platformId = ground.bodyId;
|
|
98
|
+
// Carry the rider with the crate this frame (position delta) AND inherit its
|
|
99
|
+
// velocity, mirroring the kinematic moving-platform path — otherwise the
|
|
100
|
+
// rider lags one frame and slides off a moving crate.
|
|
101
|
+
x += ground.deltaX;
|
|
102
|
+
z += ground.deltaZ;
|
|
103
|
+
vx += ground.deltaX;
|
|
104
|
+
vz += ground.deltaZ;
|
|
105
|
+
events.push('stand-on-dynamic');
|
|
106
|
+
if (ground.deltaX !== 0 || ground.deltaZ !== 0) {
|
|
107
|
+
events.push('platform-velocity-transfer');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Applied BEFORE the horizontal slide: the slide resolves overlap horizontally
|
|
112
|
+
// only, so a capsule that fell into terrain would be shoved sideways across
|
|
113
|
+
// the surface instead of being stood on top of it. Skipped when a dynamic
|
|
114
|
+
// body already claimed a higher surface this frame.
|
|
115
|
+
// platformId carries over from the previous frame, so a rider that simply
|
|
116
|
+
// walked off keeps pointing at a platform it has left. Clear it once nothing
|
|
117
|
+
// has claimed it this frame.
|
|
118
|
+
if (options.standOnDynamicBodies === true && !platformClaimedThisFrame) {
|
|
119
|
+
platformId = undefined;
|
|
120
|
+
}
|
|
121
|
+
// `grounded` here means an earlier source (moving platform, slope, step, or a
|
|
122
|
+
// dynamic body) already claimed a surface this frame; static only wins if it
|
|
123
|
+
// is higher. platformId cannot be used for this — it carries over from the
|
|
124
|
+
// previous frame, so stepping off a crate onto terrain would skip grounding.
|
|
125
|
+
if (staticGround !== undefined && (!grounded || staticGround.topY > y)) {
|
|
126
|
+
platformId = undefined;
|
|
127
|
+
y = staticGround.topY;
|
|
128
|
+
vy = 0;
|
|
129
|
+
grounded = true;
|
|
130
|
+
groundedOnStatic = true;
|
|
131
|
+
events.push('static-ground');
|
|
132
|
+
}
|
|
78
133
|
// The KCC's intended (pre-slide) horizontal position and velocity — push
|
|
79
134
|
// impulses are computed here because the slide blocks the KCC off the body.
|
|
80
135
|
const pushCandidate = { x, z, vx, vz };
|
|
@@ -90,32 +145,41 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
90
145
|
vz = resolved.vz;
|
|
91
146
|
correctionCount += resolved.correctionCount;
|
|
92
147
|
events.push(resolved.swept ? 'physics-sweep' : resolved.slid ? 'physics-slide' : 'physics-correction');
|
|
148
|
+
// The slide moved the footprint, so the height sampled before it belongs
|
|
149
|
+
// to the old x/z. On sloped ground that stale height reads as penetration
|
|
150
|
+
// next frame and freezes movement along walls and ledges — re-sample.
|
|
151
|
+
// Attachment must follow the FINAL footprint. A slide can carry the
|
|
152
|
+
// capsule off the platform it was standing on (leaving it "grounded" in
|
|
153
|
+
// mid-air) or onto one it had missed.
|
|
154
|
+
if (groundedOnDynamic && (x !== pushCandidate.x || z !== pushCandidate.z)) {
|
|
155
|
+
const reattached = dynamicGroundUnderKcc(options.physics, x, y, z, geometry, new Set(options.ignoredPhysicsBodyIds ?? []));
|
|
156
|
+
if (reattached === undefined) {
|
|
157
|
+
grounded = false;
|
|
158
|
+
platformId = undefined;
|
|
159
|
+
groundedOnDynamic = false;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
y = reattached.topY;
|
|
163
|
+
platformId = reattached.bodyId;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (groundedOnStatic && (x !== pushCandidate.x || z !== pushCandidate.z)) {
|
|
167
|
+
const reground = staticGroundUnderKcc(options.physics, x, y, z,
|
|
168
|
+
// The capsule has already landed, so this is a local correction from
|
|
169
|
+
// the landed feet — not the start-of-frame ones. Reusing those would
|
|
170
|
+
// reopen the fall-crossing window and let a slide under an overhang
|
|
171
|
+
// yank the capsule onto its roof.
|
|
172
|
+
y - kccFeetOffset(geometry), geometry, new Set(options.ignoredPhysicsBodyIds ?? []), world.stepHeight);
|
|
173
|
+
if (reground !== undefined) {
|
|
174
|
+
y = reground.topY;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
93
177
|
}
|
|
94
178
|
}
|
|
95
179
|
const geometryForInteraction = normalizeKccGeometry3D(next);
|
|
96
180
|
let physicsImpulses = [];
|
|
97
181
|
// Stand on a dynamic body directly beneath the feet: attach as a platform and
|
|
98
182
|
// inherit its per-frame delta (its velocity), mirroring the kinematic path.
|
|
99
|
-
if (options.physics !== undefined && options.standOnDynamicBodies === true && vy <= 0) {
|
|
100
|
-
const ground = dynamicGroundUnderKcc(options.physics, x, y, z, geometryForInteraction, new Set(options.ignoredPhysicsBodyIds ?? []));
|
|
101
|
-
if (ground !== undefined) {
|
|
102
|
-
y = ground.topY;
|
|
103
|
-
vy = 0;
|
|
104
|
-
grounded = true;
|
|
105
|
-
platformId = ground.bodyId;
|
|
106
|
-
// Carry the rider with the crate this frame (position delta) AND inherit its
|
|
107
|
-
// velocity, mirroring the kinematic moving-platform path — otherwise the
|
|
108
|
-
// rider lags one frame and slides off a moving crate.
|
|
109
|
-
x += ground.deltaX;
|
|
110
|
-
z += ground.deltaZ;
|
|
111
|
-
vx += ground.deltaX;
|
|
112
|
-
vz += ground.deltaZ;
|
|
113
|
-
events.push('stand-on-dynamic');
|
|
114
|
-
if (ground.deltaX !== 0 || ground.deltaZ !== 0) {
|
|
115
|
-
events.push('platform-velocity-transfer');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
183
|
// Push the dynamic bodies the KCC walked into this frame.
|
|
120
184
|
if (options.physics !== undefined && (options.maxPushImpulse ?? 0) > 0) {
|
|
121
185
|
physicsImpulses = computeKccPushImpulses(options.physics, pushCandidate.x, y, pushCandidate.z, { vx: pushCandidate.vx, vz: pushCandidate.vz }, geometryForInteraction, options.maxPushImpulse ?? 0, new Set(options.ignoredPhysicsBodyIds ?? []));
|
|
@@ -123,7 +187,10 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
123
187
|
events.push('push-dynamic');
|
|
124
188
|
}
|
|
125
189
|
}
|
|
126
|
-
|
|
190
|
+
// Spread `next` first: the capsule's radius/halfHeight/skinWidth live on the
|
|
191
|
+
// body, and rebuilding it from a bare literal dropped them, so a caller's
|
|
192
|
+
// capsule silently reverted to the 1x2 default after a single step.
|
|
193
|
+
const boundsCorrection = clampBodyToBounds({ ...next, x, y, z, vx, vy, vz, grounded, coyoteFrames, jumpBufferFrames, platformId }, world);
|
|
127
194
|
if (boundsCorrection.corrected) {
|
|
128
195
|
correctionCount += boundsCorrection.corrections;
|
|
129
196
|
events.push(...boundsCorrection.events);
|
|
@@ -247,7 +314,101 @@ function groundContactTopAt(body, x, z) {
|
|
|
247
314
|
}
|
|
248
315
|
return top;
|
|
249
316
|
}
|
|
250
|
-
|
|
317
|
+
if (shape.type === 'hull') {
|
|
318
|
+
// Not primitiveHalfExtent: that returns symmetric max(|y|) half-extents, so
|
|
319
|
+
// an asymmetric hull would snap the capsule above its real top and treat
|
|
320
|
+
// the mirrored side of the AABB as walkable. The stand-on height is max(y)
|
|
321
|
+
// of the rotated vertices, and the footprint is their true XZ range.
|
|
322
|
+
const m = quatToMat3(q);
|
|
323
|
+
let minX = Infinity;
|
|
324
|
+
let maxX = -Infinity;
|
|
325
|
+
let minZ = Infinity;
|
|
326
|
+
let maxZ = -Infinity;
|
|
327
|
+
let maxY = -Infinity;
|
|
328
|
+
for (const vertex of shape.vertices) {
|
|
329
|
+
const rotated = mulMat3Vec(m, vertex);
|
|
330
|
+
minX = Math.min(minX, rotated.x);
|
|
331
|
+
maxX = Math.max(maxX, rotated.x);
|
|
332
|
+
minZ = Math.min(minZ, rotated.z);
|
|
333
|
+
maxZ = Math.max(maxZ, rotated.z);
|
|
334
|
+
maxY = Math.max(maxY, rotated.y);
|
|
335
|
+
}
|
|
336
|
+
const localX = x - body.x;
|
|
337
|
+
const localZ = z - body.z;
|
|
338
|
+
if (localX < minX || localX > maxX || localZ < minZ || localZ > maxZ) {
|
|
339
|
+
return undefined;
|
|
340
|
+
}
|
|
341
|
+
return body.y + maxY;
|
|
342
|
+
}
|
|
343
|
+
if (shape.type === 'heightfield') {
|
|
344
|
+
// Heightfields are static and never rotate (the solver maps them to a mesh
|
|
345
|
+
// in body-local space), so local == world minus the body position.
|
|
346
|
+
const localY = heightfieldLocalSurfaceY(shape, x - body.x, z - body.z);
|
|
347
|
+
return localY === undefined ? undefined : body.y + localY;
|
|
348
|
+
}
|
|
349
|
+
return undefined; // mesh: arbitrary triangle soup, not a stand-on platform
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Highest static surface under the capsule within `snapDistance` of its feet.
|
|
353
|
+
*
|
|
354
|
+
* The overlap probe only narrows candidates (it rides the world's static query
|
|
355
|
+
* tree); the surface height itself comes from groundContactTopAt, so a capsule
|
|
356
|
+
* lands on the same triangle a projectile would hit.
|
|
357
|
+
*/
|
|
358
|
+
/**
|
|
359
|
+
* Separation held between a grounded capsule and the surface under it, so
|
|
360
|
+
* resting contact never reads as penetration. Fixed rather than derived because
|
|
361
|
+
* skinWidth defaults to 0; a plain double is bit-identical on every peer.
|
|
362
|
+
*/
|
|
363
|
+
const KCC_GROUND_CLEARANCE = 1e-4;
|
|
364
|
+
/**
|
|
365
|
+
* Distance from the capsule's centre to the bottom of its collision shape.
|
|
366
|
+
* A capsule's vertical extent is halfHeight + radius (see primitiveHalfExtent),
|
|
367
|
+
* so grounding on `halfHeight` alone buries it by a radius and the horizontal
|
|
368
|
+
* slide then fights the penetration every frame — the capsule cannot walk.
|
|
369
|
+
* skinWidth keeps a gap so resting contact is not treated as overlap.
|
|
370
|
+
*/
|
|
371
|
+
function kccFeetOffset(geometry) {
|
|
372
|
+
// physicsOverlapsKcc probes with radius+skinWidth AND halfHeight+skinWidth,
|
|
373
|
+
// and counts exact touching as an overlap — at which point the slide reverts
|
|
374
|
+
// the whole horizontal move, so a capsule resting flush on terrain cannot
|
|
375
|
+
// walk. Rest one clearance above that probe instead of on the surface.
|
|
376
|
+
return geometry.halfHeight + geometry.radius + 2 * geometry.skinWidth + KCC_GROUND_CLEARANCE;
|
|
377
|
+
}
|
|
378
|
+
function staticGroundUnderKcc(physics, x, y, z, previousFeetY, geometry, ignoredBodyIds, snapDistance) {
|
|
379
|
+
const feetY = y - kccFeetOffset(geometry);
|
|
380
|
+
// Gravity is unbounded (vy -= 1 per frame), so a fast fall can clear the whole
|
|
381
|
+
// snap band in one step. Accept a surface the feet CROSSED this frame, not
|
|
382
|
+
// just one they ended near — otherwise terrain tunnels at terminal velocity.
|
|
383
|
+
// Requiring the frame to START at or above the surface is what keeps a capsule
|
|
384
|
+
// walking beneath a floating static box from being yanked onto its roof.
|
|
385
|
+
const fallDistance = Math.max(0, previousFeetY - feetY);
|
|
386
|
+
const reach = snapDistance + fallDistance;
|
|
387
|
+
const probe = {
|
|
388
|
+
shape: { type: 'sphere', radius: geometry.radius + reach },
|
|
389
|
+
x,
|
|
390
|
+
y: feetY,
|
|
391
|
+
z,
|
|
392
|
+
hitStatics: true,
|
|
393
|
+
hitDynamics: false,
|
|
394
|
+
hitKinematics: false,
|
|
395
|
+
};
|
|
396
|
+
const hitIds = new Set(overlapDeterministicPhysics3D(physics, probe));
|
|
397
|
+
const grounds = physics.bodies
|
|
398
|
+
.filter((body) => body.kind === 'static' && hitIds.has(body.id) && !ignoredBodyIds.has(body.id))
|
|
399
|
+
.map((body) => ({ body, top: groundContactTopAt(body, x, z) }))
|
|
400
|
+
.filter((candidate) => candidate.top !== undefined
|
|
401
|
+
&& feetY <= candidate.top + snapDistance
|
|
402
|
+
&& previousFeetY >= candidate.top - snapDistance)
|
|
403
|
+
.sort((left, right) => right.top - left.top || compareStrings(left.body.id, right.body.id));
|
|
404
|
+
const ground = grounds[0];
|
|
405
|
+
if (ground === undefined) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
bodyId: ground.body.id,
|
|
410
|
+
topY: ground.top + kccFeetOffset(geometry),
|
|
411
|
+
};
|
|
251
412
|
}
|
|
252
413
|
/** World half-extents (x,y,z) of a primitive shape under rotation matrix `m`. */
|
|
253
414
|
function primitiveHalfExtent(shape, m) {
|
|
@@ -283,7 +444,7 @@ function primitiveHalfExtent(shape, m) {
|
|
|
283
444
|
}
|
|
284
445
|
/** A dynamic body directly under the KCC feet, with its per-frame delta. */
|
|
285
446
|
function dynamicGroundUnderKcc(physics, x, y, z, geometry, ignoredBodyIds) {
|
|
286
|
-
const feetY = y - geometry
|
|
447
|
+
const feetY = y - kccFeetOffset(geometry);
|
|
287
448
|
const probe = {
|
|
288
449
|
shape: { type: 'sphere', radius: geometry.radius },
|
|
289
450
|
x,
|
|
@@ -308,7 +469,7 @@ function dynamicGroundUnderKcc(physics, x, y, z, geometry, ignoredBodyIds) {
|
|
|
308
469
|
}
|
|
309
470
|
return {
|
|
310
471
|
bodyId: ground.body.id,
|
|
311
|
-
topY: ground.top + geometry
|
|
472
|
+
topY: ground.top + kccFeetOffset(geometry),
|
|
312
473
|
deltaX: ground.body.vx,
|
|
313
474
|
deltaZ: ground.body.vz,
|
|
314
475
|
};
|
|
@@ -14,4 +14,14 @@ export interface HeightfieldBounds3D {
|
|
|
14
14
|
readonly maxZ: number;
|
|
15
15
|
}
|
|
16
16
|
export declare function heightfieldLocalBounds(shape: HeightfieldShapeData3D): HeightfieldBounds3D;
|
|
17
|
+
/**
|
|
18
|
+
* Local surface height at a local (x, z), or undefined outside the field.
|
|
19
|
+
*
|
|
20
|
+
* MUST stay consistent with heightfieldLocalTriangles' cell split: a cell emits
|
|
21
|
+
* [a,b,c] and [d,c,b] with a=(col,row) b=(col,row+1) c=(col+1,row)
|
|
22
|
+
* d=(col+1,row+1), so the diagonal runs b–c and the first triangle is the
|
|
23
|
+
* u+v<=1 half. Sampling a different diagonal would let a character stand where
|
|
24
|
+
* a projectile passes through.
|
|
25
|
+
*/
|
|
26
|
+
export declare function heightfieldLocalSurfaceY(shape: HeightfieldShapeData3D, localX: number, localZ: number): number | undefined;
|
|
17
27
|
export declare function heightfieldLocalTriangles(shape: HeightfieldShapeData3D, bounds?: HeightfieldBounds3D): readonly (readonly [Point3D, Point3D, Point3D])[];
|
|
@@ -21,6 +21,39 @@ function vertex(shape, column, row) {
|
|
|
21
21
|
z: row * shape.scale.z,
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
function heightAt(shape, column, row) {
|
|
25
|
+
return shape.heights[row * shape.columns + column] * shape.scale.y;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Local surface height at a local (x, z), or undefined outside the field.
|
|
29
|
+
*
|
|
30
|
+
* MUST stay consistent with heightfieldLocalTriangles' cell split: a cell emits
|
|
31
|
+
* [a,b,c] and [d,c,b] with a=(col,row) b=(col,row+1) c=(col+1,row)
|
|
32
|
+
* d=(col+1,row+1), so the diagonal runs b–c and the first triangle is the
|
|
33
|
+
* u+v<=1 half. Sampling a different diagonal would let a character stand where
|
|
34
|
+
* a projectile passes through.
|
|
35
|
+
*/
|
|
36
|
+
export function heightfieldLocalSurfaceY(shape, localX, localZ) {
|
|
37
|
+
const maxX = (shape.columns - 1) * shape.scale.x;
|
|
38
|
+
const maxZ = (shape.rows - 1) * shape.scale.z;
|
|
39
|
+
if (localX < 0 || localX > maxX || localZ < 0 || localZ > maxZ) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
const columnFloat = localX / shape.scale.x;
|
|
43
|
+
const rowFloat = localZ / shape.scale.z;
|
|
44
|
+
const column = Math.min(shape.columns - 2, Math.floor(columnFloat));
|
|
45
|
+
const row = Math.min(shape.rows - 2, Math.floor(rowFloat));
|
|
46
|
+
const u = columnFloat - column;
|
|
47
|
+
const v = rowFloat - row;
|
|
48
|
+
const a = heightAt(shape, column, row);
|
|
49
|
+
const b = heightAt(shape, column, row + 1);
|
|
50
|
+
const c = heightAt(shape, column + 1, row);
|
|
51
|
+
if (u + v <= 1) {
|
|
52
|
+
return a + (b - a) * v + (c - a) * u;
|
|
53
|
+
}
|
|
54
|
+
const d = heightAt(shape, column + 1, row + 1);
|
|
55
|
+
return d + (c - d) * (1 - v) + (b - d) * (1 - u);
|
|
56
|
+
}
|
|
24
57
|
export function heightfieldLocalTriangles(shape, bounds) {
|
|
25
58
|
const minColumn = bounds === undefined ? 0 : Math.max(0, Math.floor(bounds.minX / shape.scale.x));
|
|
26
59
|
const maxColumn = bounds === undefined
|
package/dist/physics3d-hull.d.ts
CHANGED
|
@@ -51,3 +51,17 @@ export declare function closestPointOnHullLocal(point: Point3D, hull: SolverHull
|
|
|
51
51
|
readonly normal: Point3D;
|
|
52
52
|
readonly distance: number;
|
|
53
53
|
};
|
|
54
|
+
/**
|
|
55
|
+
* Vertices for a cylinder approximated as a regular prism, for trunks, stumps,
|
|
56
|
+
* logs, pillars and barrels.
|
|
57
|
+
*
|
|
58
|
+
* There is no cylinder primitive in Kinetix, the TypeScript solver, or the
|
|
59
|
+
* Box3D parity set — a cylinder is a convex hull, and hulls are at parity. The
|
|
60
|
+
* TypeScript hull builder accepts 4-16 vertices, so `sides` tops out at 8 (an
|
|
61
|
+
* octagonal prism, exactly 16). A capsule is cheaper but its rounded caps let a
|
|
62
|
+
* character slide off a flat top and change how a body rolls off an edge.
|
|
63
|
+
*
|
|
64
|
+
* Call this when building the world, not inside the step: the vertices are
|
|
65
|
+
* constant for a given (radius, halfHeight, sides).
|
|
66
|
+
*/
|
|
67
|
+
export declare function cylinderHullVertices(radius: number, halfHeight: number, sides?: number): readonly Point3D[];
|
package/dist/physics3d-hull.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Numeric regime: add/mul/cmp plus `solverSqrt` for the final unit normals only.
|
|
11
11
|
* No transcendentals (banned in src/ by the static determinism checker).
|
|
12
12
|
*/
|
|
13
|
+
import { createDeterministicMath } from './math.js';
|
|
13
14
|
import { addVec3, crossVec3, dotVec3, scaleVec3, solverSqrt, subVec3, } from './physics3d-shared.js';
|
|
14
15
|
const PLANE_TOL = 1e-9;
|
|
15
16
|
const COPLANAR_TOL = 1e-6;
|
|
@@ -374,3 +375,47 @@ function closestPointOnSegmentLocal(point, a, b) {
|
|
|
374
375
|
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
375
376
|
return addVec3(a, scaleVec3(ab, t));
|
|
376
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Native Math.cos/Math.sin are not bit-identical across JS engines, and these
|
|
380
|
+
* vertices become checksummed collision geometry — two peers building the same
|
|
381
|
+
* trunk must agree exactly. sinTurns/cosTurns are pure integer arithmetic.
|
|
382
|
+
* 840 is divisible by every allowed side count, so each vertex lands on an
|
|
383
|
+
* exact turn boundary.
|
|
384
|
+
*/
|
|
385
|
+
const PRISM_TURN_SCALE = 840_000;
|
|
386
|
+
const prismMath = createDeterministicMath(PRISM_TURN_SCALE);
|
|
387
|
+
/**
|
|
388
|
+
* Vertices for a cylinder approximated as a regular prism, for trunks, stumps,
|
|
389
|
+
* logs, pillars and barrels.
|
|
390
|
+
*
|
|
391
|
+
* There is no cylinder primitive in Kinetix, the TypeScript solver, or the
|
|
392
|
+
* Box3D parity set — a cylinder is a convex hull, and hulls are at parity. The
|
|
393
|
+
* TypeScript hull builder accepts 4-16 vertices, so `sides` tops out at 8 (an
|
|
394
|
+
* octagonal prism, exactly 16). A capsule is cheaper but its rounded caps let a
|
|
395
|
+
* character slide off a flat top and change how a body rolls off an edge.
|
|
396
|
+
*
|
|
397
|
+
* Call this when building the world, not inside the step: the vertices are
|
|
398
|
+
* constant for a given (radius, halfHeight, sides).
|
|
399
|
+
*/
|
|
400
|
+
export function cylinderHullVertices(radius, halfHeight, sides = 8) {
|
|
401
|
+
if (!Number.isFinite(radius) || radius <= 0) {
|
|
402
|
+
throw new Error(`cylinder hull radius must be a finite positive number, received ${radius}`);
|
|
403
|
+
}
|
|
404
|
+
if (!Number.isFinite(halfHeight) || halfHeight <= 0) {
|
|
405
|
+
throw new Error(`cylinder hull halfHeight must be a finite positive number, received ${halfHeight}`);
|
|
406
|
+
}
|
|
407
|
+
if (!Number.isInteger(sides) || sides < 3 || sides > 8) {
|
|
408
|
+
throw new Error(`cylinder hull sides must be an integer in 3..8 (2 x sides must fit the 4-16 vertex hull limit), received ${sides}`);
|
|
409
|
+
}
|
|
410
|
+
const vertices = [];
|
|
411
|
+
for (let index = 0; index < sides; index += 1) {
|
|
412
|
+
// Turns, not radians: PRISM_TURN_SCALE is divisible by every allowed side
|
|
413
|
+
// count, so this is an exact integer and never rounds.
|
|
414
|
+
const turns = (PRISM_TURN_SCALE * index) / sides;
|
|
415
|
+
const x = (radius * prismMath.cosTurns(turns)) / PRISM_TURN_SCALE;
|
|
416
|
+
const z = (radius * prismMath.sinTurns(turns)) / PRISM_TURN_SCALE;
|
|
417
|
+
vertices.push({ x, y: halfHeight, z });
|
|
418
|
+
vertices.push({ x, y: -halfHeight, z });
|
|
419
|
+
}
|
|
420
|
+
return vertices;
|
|
421
|
+
}
|
package/dist/physics3d.js
CHANGED
|
@@ -2080,6 +2080,33 @@ function warmMapToCache(warm, bodies) {
|
|
|
2080
2080
|
|| left.anchorY - right.anchorY
|
|
2081
2081
|
|| left.anchorZ - right.anchorZ);
|
|
2082
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Exact-overlap memo for static/static pairs. Neither body can move, so the
|
|
2085
|
+
* result is invariant for the lifetime of both objects, and static bodies keep
|
|
2086
|
+
* their identity across steps (see the `kind === 'static'` passthrough in the
|
|
2087
|
+
* solver's body rebuild). Without this, every prop resting on a heightfield
|
|
2088
|
+
* regenerates the terrain's triangles on every step to re-derive an answer that
|
|
2089
|
+
* cannot change.
|
|
2090
|
+
*/
|
|
2091
|
+
// rdm-ignore-next-line determinism/no-mutable-collection-state: pure derived overlap memo keyed by immutable body identity
|
|
2092
|
+
const staticOverlapCache = new WeakMap();
|
|
2093
|
+
function shapesOverlapForPair(left, right) {
|
|
2094
|
+
if (left.kind !== 'static' || right.kind !== 'static') {
|
|
2095
|
+
return shapesOverlap3D(bodyPose(left), bodyPose(right));
|
|
2096
|
+
}
|
|
2097
|
+
let memo = staticOverlapCache.get(left);
|
|
2098
|
+
if (memo === undefined) {
|
|
2099
|
+
memo = new WeakMap();
|
|
2100
|
+
staticOverlapCache.set(left, memo);
|
|
2101
|
+
}
|
|
2102
|
+
const cached = memo.get(right);
|
|
2103
|
+
if (cached !== undefined) {
|
|
2104
|
+
return cached;
|
|
2105
|
+
}
|
|
2106
|
+
const overlap = shapesOverlap3D(bodyPose(left), bodyPose(right));
|
|
2107
|
+
memo.set(right, overlap);
|
|
2108
|
+
return overlap;
|
|
2109
|
+
}
|
|
2083
2110
|
function detectContacts(bodies, keepPair) {
|
|
2084
2111
|
const contacts = [];
|
|
2085
2112
|
for (const pair of broadphaseCandidatePairs(bodies)) {
|
|
@@ -2091,7 +2118,7 @@ function detectContacts(bodies, keepPair) {
|
|
|
2091
2118
|
if ((left.mask & right.layer) === 0 ||
|
|
2092
2119
|
(right.mask & left.layer) === 0 ||
|
|
2093
2120
|
!boundsOverlap(pair.leftBounds, pair.rightBounds) ||
|
|
2094
|
-
!
|
|
2121
|
+
!shapesOverlapForPair(left, right)) {
|
|
2095
2122
|
continue;
|
|
2096
2123
|
}
|
|
2097
2124
|
const pairId = [left.id, right.id].sort(compareStrings).join('|');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/rundot-syncplay",
|
|
3
|
-
"version": "5.26.0-beta.
|
|
3
|
+
"version": "5.26.0-beta.3",
|
|
4
4
|
"description": "Kinetix orchestration, rollback, replay, late-join, and deterministic networking for RUN.game",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
"types": "./core/physics.d.ts",
|
|
71
71
|
"default": "./core/src/physics/index.mjs"
|
|
72
72
|
},
|
|
73
|
+
"./core/box3d-probe": "./core/src/physics/box3d-probe.mjs",
|
|
73
74
|
"./core/build-native": "./core/scripts/build-native.mjs",
|
|
75
|
+
"./core/build-mobile-native": "./core/scripts/build-mobile-native.mjs",
|
|
74
76
|
"./core/build-wasm-physics": "./core/scripts/build-wasm-physics.mjs",
|
|
75
77
|
"./core/preflight": "./core/scripts/preflight.mjs"
|
|
76
78
|
},
|
|
@@ -88,17 +90,19 @@
|
|
|
88
90
|
"scripts": {
|
|
89
91
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
90
92
|
"test": "npm run test:core",
|
|
91
|
-
"test:core": "tsx --test tests/canonical.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/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/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/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/platformer-content.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",
|
|
93
|
+
"test:core": "tsx --test tests/canonical.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/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/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/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/platformer-content.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/movement3d-kcc-unit-contract.test.ts tests/movement3d-static-ground.test.ts",
|
|
92
94
|
"test:soak": "tsx --test tests/runtime-soak.test.ts",
|
|
93
|
-
"test:extended": "tsx --test 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-destruction-cert.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-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.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",
|
|
95
|
+
"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-destruction-cert.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-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.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.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",
|
|
94
96
|
"test:ci": "npm run build && npm run test:core",
|
|
95
97
|
"build:native": "node core/scripts/build-native.mjs",
|
|
98
|
+
"build:mobile-native": "node core/scripts/build-mobile-native.mjs",
|
|
96
99
|
"build:wasm-physics": "node core/scripts/build-wasm-physics.mjs",
|
|
97
100
|
"test:determinism": "node --test core/tests/*.test.mjs",
|
|
98
101
|
"generate:platformer-manifest": "node core/scripts/generate-platformer2d-manifest.mjs",
|
|
99
102
|
"generate:physics-vectors": "node scripts/gen-physics-vectors.mjs",
|
|
100
103
|
"test:physics-vectors": "node --import tsx/esm --test tests/physics-vector-generator.test.ts",
|
|
101
|
-
"pack:dry-run": "npm pack --dry-run"
|
|
104
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
105
|
+
"bench:cross-lane": "node core/bench/cross-lane.mjs"
|
|
102
106
|
},
|
|
103
107
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
104
108
|
"keywords": [
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export type PredictionCullingMode = 'off' | 'local-area';
|
|
2
|
-
export interface PredictionCullingOptions {
|
|
3
|
-
readonly mode: PredictionCullingMode;
|
|
4
|
-
readonly players: number;
|
|
5
|
-
readonly objectsPerPlayer: number;
|
|
6
|
-
readonly frames: number;
|
|
7
|
-
readonly seed: number;
|
|
8
|
-
readonly radius: number;
|
|
9
|
-
readonly includeHiddenThreat?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface PredictionCullingResult {
|
|
12
|
-
readonly mode: PredictionCullingMode;
|
|
13
|
-
readonly players: number;
|
|
14
|
-
readonly objects: number;
|
|
15
|
-
readonly frames: number;
|
|
16
|
-
readonly seed: number;
|
|
17
|
-
readonly radius: number;
|
|
18
|
-
readonly averagePredictedObjects: string;
|
|
19
|
-
readonly minPredictedObjects: number;
|
|
20
|
-
readonly maxPredictedObjects: number;
|
|
21
|
-
readonly visibleCorrections: number;
|
|
22
|
-
readonly hiddenAuthoritativeInteractions: number;
|
|
23
|
-
readonly predictedObjectSteps: number;
|
|
24
|
-
readonly verifiedObjectSteps: number;
|
|
25
|
-
readonly culledObjectSteps: number;
|
|
26
|
-
readonly restoreFrame: number;
|
|
27
|
-
readonly predictedChecksum: string;
|
|
28
|
-
readonly verifiedChecksum: string;
|
|
29
|
-
readonly resimulatedChecksum: string;
|
|
30
|
-
readonly predictionFilterChecksum: string;
|
|
31
|
-
readonly completeChecksum: string;
|
|
32
|
-
readonly result: 'pass' | 'fail';
|
|
33
|
-
}
|
|
34
|
-
export declare function runPredictionCullingLab(options: PredictionCullingOptions): PredictionCullingResult;
|