@series-inc/rundot-syncplay 5.26.0-beta.3 → 5.26.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/native/mobile/syncplay_native_probe.cpp +31 -0
- package/core/native/mobile/syncplay_native_probe_abi.h +5 -0
- package/core/scripts/build-mobile-native.mjs +2 -0
- package/dist/browser.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/movement3d.d.ts +22 -0
- package/dist/movement3d.js +83 -9
- package/package.json +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#include "syncplay_native_probe_abi.h"
|
|
2
2
|
|
|
3
|
+
#include "../fixed_math.hpp"
|
|
3
4
|
#include "../physics/physics.hpp"
|
|
4
5
|
#include "box3d_probe_scenario.hpp"
|
|
5
6
|
|
|
7
|
+
#include <bit>
|
|
6
8
|
#include <chrono>
|
|
7
9
|
#include <cstdint>
|
|
8
10
|
#include <mutex>
|
|
@@ -40,6 +42,35 @@ extern "C" std::int32_t rundot_syncplay_native_probe_run(
|
|
|
40
42
|
output->status = RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_UNSUPPORTED_ABI;
|
|
41
43
|
return output->status;
|
|
42
44
|
}
|
|
45
|
+
if (input->scenario_id == RUNDOT_SYNCPLAY_NATIVE_PROBE_SCENARIO_FIXED_MATH_V1) {
|
|
46
|
+
if (input->frames == 0 || input->frames > 1000000) {
|
|
47
|
+
output->status = RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_INVALID_FRAMES;
|
|
48
|
+
return output->status;
|
|
49
|
+
}
|
|
50
|
+
namespace fx = rundot::fixedmath;
|
|
51
|
+
const fx::Config cfg = fx::defaultConfig();
|
|
52
|
+
bool ok = true;
|
|
53
|
+
fx::Vec3 p{0, 0, 0};
|
|
54
|
+
fx::Vec3 v{1000, 2000, -500};
|
|
55
|
+
double acc = 0;
|
|
56
|
+
const auto mathStart = std::chrono::steady_clock::now();
|
|
57
|
+
for (std::uint32_t i = 0; i < input->frames; i += 1) {
|
|
58
|
+
p = fx::vec3Add(cfg, p, fx::vec3Scale(cfg, v, 990, ok), ok);
|
|
59
|
+
acc += fx::vec3Length(cfg, p, ok);
|
|
60
|
+
v = fx::vec3Scale(cfg, v, 999, ok);
|
|
61
|
+
if (p.x > 2000000000.0) p = fx::Vec3{0, 0, 0};
|
|
62
|
+
}
|
|
63
|
+
output->elapsed_ns = static_cast<std::uint64_t>(
|
|
64
|
+
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
65
|
+
std::chrono::steady_clock::now() - mathStart).count());
|
|
66
|
+
output->completed_frames = input->frames;
|
|
67
|
+
// The digest IS the f64 bit pattern, so it compares directly against the
|
|
68
|
+
// JavaScript lane's DataView-derived value with no formatting in between.
|
|
69
|
+
output->final_checksum = std::bit_cast<std::uint64_t>(acc);
|
|
70
|
+
output->status = ok ? RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_OK
|
|
71
|
+
: RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_STEP_FAILED;
|
|
72
|
+
return output->status;
|
|
73
|
+
}
|
|
43
74
|
if (input->scenario_id != RUNDOT_SYNCPLAY_NATIVE_PROBE_SCENARIO_BOX3D_V1) {
|
|
44
75
|
output->status = RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_UNSUPPORTED_SCENARIO;
|
|
45
76
|
return output->status;
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
#define RUNDOT_SYNCPLAY_NATIVE_PROBE_ABI_VERSION 1u
|
|
6
6
|
#define RUNDOT_SYNCPLAY_NATIVE_PROBE_SCENARIO_BOX3D_V1 1u
|
|
7
|
+
// Runs the fixed-point math kernel and returns its f64-bit digest in
|
|
8
|
+
// final_checksum. Exists so a device can compare its C++ result against the same
|
|
9
|
+
// kernel run in the app's JavaScript engine: with interpreted glue allowed on
|
|
10
|
+
// Android, that engine is on the consensus path and must agree bit-for-bit.
|
|
11
|
+
#define RUNDOT_SYNCPLAY_NATIVE_PROBE_SCENARIO_FIXED_MATH_V1 2u
|
|
7
12
|
|
|
8
13
|
#define RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_OK 0
|
|
9
14
|
#define RUNDOT_SYNCPLAY_NATIVE_PROBE_STATUS_INVALID_ARGUMENT (-1)
|
package/dist/browser.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export type { DeterministicAuthoritativeLagCompensation3DShotContext, Determinis
|
|
|
61
61
|
export type { DeterministicLocalAuthoritySnapshotIdentity, DeterministicLocalAuthoritySnapshotTransfer, DeterministicLocalAuthorityWireEnvelope, DeterministicLocalAuthorityWireKind } from './local-authority-wire.js';
|
|
62
62
|
export type { DeterministicFixedQuaternion, DeterministicFixedVec2, DeterministicFixedVec3, DeterministicMath } from './math.js';
|
|
63
63
|
export type { DeterministicFpsAimState, DeterministicFpsInput, DeterministicKccBody, DeterministicKccInput, DeterministicKccStepResult, DeterministicLagCompensatedHitValidation, DeterministicLagCompensatedShot, DeterministicMovementCrowdMetrics, DeterministicMovementSimulationMetrics, DeterministicMovementStep, DeterministicMovementWorld, DeterministicMovingPlatform, DeterministicOneWayPlatform, DeterministicSlope, DeterministicTargetSnapshot, DeterministicTopDownBody, DeterministicTopDownBounds, DeterministicTopDownInput } from './movement.js';
|
|
64
|
-
export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
|
|
64
|
+
export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccJumpTuning3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
|
|
65
65
|
export type { CookedDeterministicNavigationGraph, DeterministicFlowField, DeterministicNavEdge, DeterministicNavigationAgent, DeterministicNavigationGraphSource, DeterministicNavigationStepOptions, DeterministicNavigationStepResult, DeterministicNavNode, DeterministicNavRegion, DeterministicPathQuery, DeterministicPathResult, DeterministicTilemapSource } from './navigation.js';
|
|
66
66
|
export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, DeterministicNavmeshAgentStepResult, DeterministicNavmeshImportSource, DeterministicNavmeshLink, DeterministicNavmeshMovingAvoidanceObstacle, DeterministicNavmeshOffMeshTraversalState, DeterministicNavmeshOffMeshTraversalStep, DeterministicNavmeshPath, DeterministicNavmeshPoint, DeterministicNavmeshPolygon, DeterministicNavmeshSource } from './navmesh.js';
|
|
67
67
|
export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, NetworkedSyncplaySlotPresence, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode } from './networked-client.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -73,7 +73,7 @@ export type { DeterministicAuthoritativeLagCompensation3DShotContext, Determinis
|
|
|
73
73
|
export type { DeterministicLocalAuthoritySnapshotIdentity, DeterministicLocalAuthoritySnapshotTransfer, DeterministicLocalAuthorityWireEnvelope, DeterministicLocalAuthorityWireKind } from './local-authority-wire.js';
|
|
74
74
|
export type { DeterministicFixedQuaternion, DeterministicFixedVec2, DeterministicFixedVec3, DeterministicMath } from './math.js';
|
|
75
75
|
export type { DeterministicFpsAimState, DeterministicFpsInput, DeterministicKccBody, DeterministicKccInput, DeterministicKccStepResult, DeterministicMovementCrowdMetrics, DeterministicMovementSimulationMetrics, DeterministicMovementStep, DeterministicMovementWorld, DeterministicMovingPlatform, DeterministicOneWayPlatform, DeterministicSlope, DeterministicTargetSnapshot, DeterministicTopDownBody, DeterministicTopDownBounds, DeterministicTopDownInput } from './movement.js';
|
|
76
|
-
export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
|
|
76
|
+
export type { DeterministicKccBody3D, DeterministicKccCarrierPose3D, DeterministicKccCarryResult3D, DeterministicKccCarryState3D, DeterministicKccCollisionCandidate3D, DeterministicKccCollisionFilter3D, DeterministicKccExternalImpulse3D, DeterministicKccGeometry3D, DeterministicKccInput3D, DeterministicKccJumpTuning3D, DeterministicKccPhysicsImpulse3D, DeterministicKccProcessor3D, DeterministicKccStepResult3D, DeterministicKccWorld3D, DeterministicMovingPlatform3D, DeterministicSlope3D, DeterministicStep3D } from './movement3d.js';
|
|
77
77
|
export type { CookedDeterministicNavigationGraph, DeterministicFlowField, DeterministicNavEdge, DeterministicNavigationAgent, DeterministicNavigationGraphSource, DeterministicNavigationStepOptions, DeterministicNavigationStepResult, DeterministicNavNode, DeterministicNavRegion, DeterministicPathQuery, DeterministicPathResult, DeterministicTilemapSource } from './navigation.js';
|
|
78
78
|
export type { CookedDeterministicNavmesh, DeterministicNavmeshAgent, DeterministicNavmeshAgentStepResult, DeterministicNavmeshImportSource, DeterministicNavmeshLink, DeterministicNavmeshMovingAvoidanceObstacle, DeterministicNavmeshOffMeshTraversalState, DeterministicNavmeshOffMeshTraversalStep, DeterministicNavmeshPath, DeterministicNavmeshPoint, DeterministicNavmeshPolygon, DeterministicNavmeshSource } from './navmesh.js';
|
|
79
79
|
export type { ConfirmedSyncplayPresentationFrame, KinetixRuntimeFactory, NetworkedClientTransport, NetworkedSyncplayClient, NetworkedSyncplayClientInputDelayOptions, NetworkedSyncplayClientNetStats, NetworkedSyncplayClientOptions, NetworkedSyncplayClientPacingOptions, NetworkedSyncplayPreparationOptions, NetworkedSyncplaySessionDescriptor, PreparedNetworkedSyncplayTransport, SyncplayPredictionMode } from './networked-client.js';
|
package/dist/movement3d.d.ts
CHANGED
|
@@ -24,6 +24,20 @@ export interface DeterministicKccInput3D {
|
|
|
24
24
|
readonly moveZ: number;
|
|
25
25
|
readonly jumpPressed?: boolean;
|
|
26
26
|
}
|
|
27
|
+
export interface DeterministicKccJumpTuning3D {
|
|
28
|
+
/** Upward velocity the jump sets, in units per frame. Default 10. */
|
|
29
|
+
readonly impulse?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Immediate lift applied on the jump frame to unstick the capsule from the
|
|
32
|
+
* ground it is resting on. Defaults to `impulse / 10`, which keeps the
|
|
33
|
+
* historical 1-unit lift at the default impulse and scales with a retune.
|
|
34
|
+
*/
|
|
35
|
+
readonly liftOff?: number;
|
|
36
|
+
/** Frames a jump stays available after leaving the ground. Default 4. */
|
|
37
|
+
readonly coyoteFrames?: number;
|
|
38
|
+
/** Frames an early jump press is held for. Minimum 1, default 4. */
|
|
39
|
+
readonly bufferFrames?: number;
|
|
40
|
+
}
|
|
27
41
|
export interface DeterministicKccExternalImpulse3D {
|
|
28
42
|
readonly id: string;
|
|
29
43
|
readonly vx: number;
|
|
@@ -198,6 +212,14 @@ export declare function stepDeterministicKcc3D(body: DeterministicKccBody3D, inp
|
|
|
198
212
|
* bit-identical checksums.
|
|
199
213
|
*/
|
|
200
214
|
readonly groundOnStaticBodies?: boolean;
|
|
215
|
+
/**
|
|
216
|
+
* Jump tuning, in the KCC's frame units. Gravity retunes through a processor
|
|
217
|
+
* because processors run before `vy -= 1`, but the jump impulse is applied
|
|
218
|
+
* AFTER them, so it cannot be scaled the same way — without these a game that
|
|
219
|
+
* retunes gravity must own jump, coyote and buffer itself. Every default
|
|
220
|
+
* reproduces the previous hardcoded behaviour bit-for-bit.
|
|
221
|
+
*/
|
|
222
|
+
readonly jump?: DeterministicKccJumpTuning3D;
|
|
201
223
|
}): DeterministicKccStepResult3D;
|
|
202
224
|
export interface DeterministicKccCarryState3D {
|
|
203
225
|
readonly bodyId: string;
|
package/dist/movement3d.js
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { defaultChecksum } from './canonical.js';
|
|
2
2
|
import { createDeterministicPhysicsWorld3D, overlapDeterministicPhysics3D, } from './physics3d.js';
|
|
3
3
|
import { heightfieldLocalSurfaceY } from './physics3d-heightfield.js';
|
|
4
|
-
import { mulMat3Vec, quaternionFromQuarterTurns, quatMultiply, quatToMat3, rotatePointByQuat } from './physics3d-shared.js';
|
|
4
|
+
import { mulMat3Vec, quaternionFromQuarterTurns, quatMultiply, quatToMat3, rotatePointByQuat, solverSqrt } from './physics3d-shared.js';
|
|
5
|
+
function normalizeKccJumpTuning3D(tuning) {
|
|
6
|
+
const impulse = finiteKccJumpNumber(tuning?.impulse, 10, 'impulse');
|
|
7
|
+
return {
|
|
8
|
+
impulse,
|
|
9
|
+
liftOff: finiteKccJumpNumber(tuning?.liftOff, impulse / 10, 'liftOff'),
|
|
10
|
+
coyoteFrames: kccJumpFrameCount(tuning?.coyoteFrames, 'coyoteFrames', 0),
|
|
11
|
+
// A press arms the jump by writing this into jumpBufferFrames and the gate
|
|
12
|
+
// needs it > 0, so 0 would not mean "no buffering" — it would mean "never
|
|
13
|
+
// jump". 1 is the real floor: the press lives for the frame it happened on.
|
|
14
|
+
bufferFrames: kccJumpFrameCount(tuning?.bufferFrames, 'bufferFrames', 1),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function finiteKccJumpNumber(value, fallback, field) {
|
|
18
|
+
if (value === undefined) {
|
|
19
|
+
return fallback;
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(value)) {
|
|
22
|
+
throw new Error(`KCC jump.${field} must be a finite number`);
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function kccJumpFrameCount(value, field, minimum) {
|
|
27
|
+
if (value === undefined) {
|
|
28
|
+
return 4;
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isInteger(value) || value < minimum) {
|
|
31
|
+
throw new Error(`KCC jump.${field} must be an integer >= ${minimum}, got ${value}`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
5
35
|
export function stepDeterministicKcc3D(body, input, world, options) {
|
|
36
|
+
const jump = normalizeKccJumpTuning3D(options.jump);
|
|
6
37
|
let next = applyProcessors(body, input, options.frame, options.processors ?? []);
|
|
7
38
|
const events = [];
|
|
8
39
|
let correctionCount = 0;
|
|
@@ -10,8 +41,8 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
10
41
|
let collisionFilterCalls = 0;
|
|
11
42
|
let collisionFilterRejectedHits = 0;
|
|
12
43
|
const externalImpulse = combineExternalKccImpulses(options.externalImpulses ?? []);
|
|
13
|
-
let coyoteFrames = next.grounded ?
|
|
14
|
-
let jumpBufferFrames = input.jumpPressed === true ?
|
|
44
|
+
let coyoteFrames = next.grounded ? jump.coyoteFrames : Math.max(0, next.coyoteFrames - 1);
|
|
45
|
+
let jumpBufferFrames = input.jumpPressed === true ? jump.bufferFrames : Math.max(0, next.jumpBufferFrames - 1);
|
|
15
46
|
let vx = input.moveX + externalImpulse.vx;
|
|
16
47
|
let vz = input.moveZ + externalImpulse.vz;
|
|
17
48
|
let vy = next.vy - 1 + externalImpulse.vy;
|
|
@@ -70,8 +101,8 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
70
101
|
}
|
|
71
102
|
}
|
|
72
103
|
if (jumpBufferFrames > 0 && (next.grounded || next.coyoteFrames > 0 || grounded)) {
|
|
73
|
-
vy =
|
|
74
|
-
y +=
|
|
104
|
+
vy = jump.impulse;
|
|
105
|
+
y += jump.liftOff;
|
|
75
106
|
grounded = false;
|
|
76
107
|
platformId = undefined;
|
|
77
108
|
jumpBufferFrames = 0;
|
|
@@ -198,7 +229,7 @@ export function stepDeterministicKcc3D(body, input, world, options) {
|
|
|
198
229
|
next = boundsCorrection.body;
|
|
199
230
|
const finalBody = {
|
|
200
231
|
...next,
|
|
201
|
-
coyoteFrames: next.grounded ?
|
|
232
|
+
coyoteFrames: next.grounded ? jump.coyoteFrames : coyoteFrames,
|
|
202
233
|
jumpBufferFrames,
|
|
203
234
|
};
|
|
204
235
|
return {
|
|
@@ -361,6 +392,42 @@ function groundContactTopAt(body, x, z) {
|
|
|
361
392
|
* skinWidth defaults to 0; a plain double is bit-identical on every peer.
|
|
362
393
|
*/
|
|
363
394
|
const KCC_GROUND_CLEARANCE = 1e-4;
|
|
395
|
+
/**
|
|
396
|
+
* Extra vertical lift a round capsule bottom needs to rest ON a sloped surface
|
|
397
|
+
* instead of IN it.
|
|
398
|
+
*
|
|
399
|
+
* groundContactTopAt reports the height under the capsule CENTRE, but the
|
|
400
|
+
* bottom is a sphere, so on gradient `g` the contact sits uphill and a bare
|
|
401
|
+
* radius of lift buries the uphill flank by `radius * (sec θ - 1)`. Past ~1.7°
|
|
402
|
+
* that exceeds KCC_GROUND_CLEARANCE, every overlap probe (full, x-only AND
|
|
403
|
+
* z-only) hits, and resolvePhysicsSlide3D reverts the whole horizontal move.
|
|
404
|
+
*
|
|
405
|
+
* Sampled a bottom-sphere radius out: that is the footprint the sphere can
|
|
406
|
+
* touch, it reads a plane exactly at any angle, and it self-bounds the
|
|
407
|
+
* discontinuities groundContactTopAt has on some shapes (a compound's children
|
|
408
|
+
* can butt together at a height jump) — a jump of D reads as D/radius here
|
|
409
|
+
* instead of the skyward fling an epsilon probe would give. Per axis it takes
|
|
410
|
+
* the steeper of forward/backward so a sample straddling an edge cannot
|
|
411
|
+
* average its way to an under-lift and back into the freeze.
|
|
412
|
+
*/
|
|
413
|
+
function kccSlopeClearance(body, x, z, top, geometry) {
|
|
414
|
+
const step = geometry.radius + geometry.skinWidth;
|
|
415
|
+
const axisGradient = (deltaX, deltaZ) => {
|
|
416
|
+
const ahead = groundContactTopAt(body, x + deltaX, z + deltaZ);
|
|
417
|
+
const behind = groundContactTopAt(body, x - deltaX, z - deltaZ);
|
|
418
|
+
const forward = ahead === undefined ? 0 : Math.abs(ahead - top) / step;
|
|
419
|
+
const backward = behind === undefined ? 0 : Math.abs(top - behind) / step;
|
|
420
|
+
return Math.max(forward, backward);
|
|
421
|
+
};
|
|
422
|
+
const gradientX = axisGradient(step, 0);
|
|
423
|
+
const gradientZ = axisGradient(0, step);
|
|
424
|
+
const gradientSq = gradientX * gradientX + gradientZ * gradientZ;
|
|
425
|
+
if (gradientSq <= 0) {
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
// radius + skinWidth is the bottom-sphere radius physicsOverlapsKcc probes with.
|
|
429
|
+
return (geometry.radius + geometry.skinWidth) * (solverSqrt(1 + gradientSq) - 1);
|
|
430
|
+
}
|
|
364
431
|
/**
|
|
365
432
|
* Distance from the capsule's centre to the bottom of its collision shape.
|
|
366
433
|
* A capsule's vertical extent is halfHeight + radius (see primitiveHalfExtent),
|
|
@@ -397,8 +464,15 @@ function staticGroundUnderKcc(physics, x, y, z, previousFeetY, geometry, ignored
|
|
|
397
464
|
const grounds = physics.bodies
|
|
398
465
|
.filter((body) => body.kind === 'static' && hitIds.has(body.id) && !ignoredBodyIds.has(body.id))
|
|
399
466
|
.map((body) => ({ body, top: groundContactTopAt(body, x, z) }))
|
|
400
|
-
.filter((candidate) => candidate.top !== undefined
|
|
401
|
-
|
|
467
|
+
.filter((candidate) => candidate.top !== undefined)
|
|
468
|
+
.map((candidate) => ({
|
|
469
|
+
...candidate,
|
|
470
|
+
lift: kccSlopeClearance(candidate.body, x, z, candidate.top, geometry),
|
|
471
|
+
}))
|
|
472
|
+
// The band is measured from where the capsule actually STANDS, so a slope
|
|
473
|
+
// lift bigger than snapDistance cannot drop it out of its own ground the
|
|
474
|
+
// next frame and leave it cycling between grounded and falling.
|
|
475
|
+
.filter((candidate) => feetY <= candidate.top + candidate.lift + snapDistance
|
|
402
476
|
&& previousFeetY >= candidate.top - snapDistance)
|
|
403
477
|
.sort((left, right) => right.top - left.top || compareStrings(left.body.id, right.body.id));
|
|
404
478
|
const ground = grounds[0];
|
|
@@ -407,7 +481,7 @@ function staticGroundUnderKcc(physics, x, y, z, previousFeetY, geometry, ignored
|
|
|
407
481
|
}
|
|
408
482
|
return {
|
|
409
483
|
bodyId: ground.body.id,
|
|
410
|
-
topY: ground.top + kccFeetOffset(geometry),
|
|
484
|
+
topY: ground.top + kccFeetOffset(geometry) + ground.lift,
|
|
411
485
|
};
|
|
412
486
|
}
|
|
413
487
|
/** World half-extents (x,y,z) of a primitive shape under rotation matrix `m`. */
|
package/package.json
CHANGED