Skip to content

Commit e845dec

Browse files
committed
fix: use clamped timestep for Rapier participants
1 parent c1023f4 commit e845dec

7 files changed

Lines changed: 33 additions & 27 deletions

File tree

dist/esm/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/esmdev/index.js

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

dist/iife/rhodonite.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/iifedev/rhodonite.js

Lines changed: 3 additions & 3 deletions
Large diffs are not rendered by default.

src/foundation/physics/Rapier/RapierCharacterControllerStrategy.test.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -443,28 +443,29 @@ test('synchronizes a character world position changed through its parent', async
443443
expect(state.position.isEqual(Vector3.fromCopy3(5, 2, -3))).toBe(true);
444444
});
445445

446-
test('moves once per frame, reports initial grounding without landing, and synchronizes the entity', async () => {
446+
test('uses the world-clamped timestep, moves once per frame, and synchronizes the entity', async () => {
447447
await RapierPhysicsStrategy.initialize(fakeRapier());
448448
const { entity, state } = fakeEntity();
449449
const strategy = new RapierCharacterControllerStrategy();
450450
strategy.setup(entity, capsuleShape(), { maxDeltaTime: 1 });
451451
strategy.setDesiredHorizontalVelocity(Vector3.fromCopy3(2, 99, -1));
452+
const timestep = 1 / 15;
452453

453454
RapierPhysicsStrategy.update(1, 0.5);
454455
RapierPhysicsStrategy.update(1, 0.5);
455456

456457
expect(world.stepCount).toBe(1);
457458
expect(strategy.isGrounded).toBe(true);
458-
expect(state.position.x).toBeCloseTo(1);
459-
expect(state.position.z).toBeCloseTo(-0.5);
459+
expect(state.position.x).toBeCloseTo(2 * timestep);
460+
expect(state.position.z).toBeCloseTo(-timestep);
460461
expect(strategy.motionState.state).toBe('grounded');
461462
expect(strategy.motionState.horizontalSpeed).toBeCloseTo(Math.hypot(2, 1));
462-
expect(strategy.motionState.groundedDuration).toBeCloseTo(0.5);
463+
expect(strategy.motionState.groundedDuration).toBeCloseTo(timestep);
463464
expect(strategy.motionState.landingImpactSpeed).toBe(0);
464465

465466
RapierPhysicsStrategy.update(2, 0.5);
466467
expect(strategy.motionState.state).toBe('grounded');
467-
expect(strategy.motionState.groundedDuration).toBeCloseTo(1);
468+
expect(strategy.motionState.groundedDuration).toBeCloseTo(timestep * 2);
468469
expect(strategy.motionState.landingImpactSpeed).toBe(0);
469470
});
470471

@@ -473,13 +474,14 @@ test('does not report landing while settling onto the initial ground', async ()
473474
const { entity } = fakeEntity();
474475
const strategy = new RapierCharacterControllerStrategy();
475476
strategy.setup(entity, capsuleShape(), { maxDeltaTime: 1, gravity: 10 });
477+
const timestep = 1 / 15;
476478

477479
world.controller!.forceAirborne = true;
478-
RapierPhysicsStrategy.update(1, 0.1);
480+
RapierPhysicsStrategy.update(1, timestep);
479481
expect(strategy.motionState.state).toBe('falling');
480482

481483
world.controller!.forceAirborne = false;
482-
RapierPhysicsStrategy.update(2, 0.1);
484+
RapierPhysicsStrategy.update(2, timestep);
483485

484486
expect(strategy.motionState.state).toBe('grounded');
485487
expect(strategy.motionState.landingImpactSpeed).toBe(0);
@@ -490,16 +492,19 @@ test('reports landing after a real airborne movement', async () => {
490492
const { entity } = fakeEntity();
491493
const strategy = new RapierCharacterControllerStrategy();
492494
strategy.setup(entity, capsuleShape(), { maxDeltaTime: 1, gravity: 10, jumpSpeed: 1.5 });
495+
const timestep = 1 / 15;
493496

494-
RapierPhysicsStrategy.update(1, 0.1);
497+
RapierPhysicsStrategy.update(1, timestep);
495498
expect(strategy.motionState.state).toBe('grounded');
496499

497500
strategy.requestJump();
498-
RapierPhysicsStrategy.update(2, 0.1);
501+
RapierPhysicsStrategy.update(2, timestep);
499502
expect(strategy.motionState.state).toBe('rising');
500-
RapierPhysicsStrategy.update(3, 0.1);
503+
RapierPhysicsStrategy.update(3, timestep);
501504
expect(strategy.motionState.state).toBe('rising');
502-
RapierPhysicsStrategy.update(4, 0.1);
505+
RapierPhysicsStrategy.update(4, timestep);
506+
expect(strategy.motionState.state).toBe('rising');
507+
RapierPhysicsStrategy.update(5, timestep);
503508

504509
expect(strategy.motionState.state).toBe('landing');
505510
expect(strategy.motionState.landingImpactSpeed).toBeCloseTo(0.5);
@@ -510,15 +515,16 @@ test('jumps only after grounding and releases Rapier resources', async () => {
510515
const { entity, state } = fakeEntity();
511516
const strategy = new RapierCharacterControllerStrategy();
512517
strategy.setup(entity, capsuleShape(), { maxDeltaTime: 1, gravity: 10, jumpSpeed: 4 });
513-
RapierPhysicsStrategy.update(1, 0.1);
518+
const timestep = 1 / 15;
519+
RapierPhysicsStrategy.update(1, timestep);
514520

515521
strategy.requestJump();
516-
RapierPhysicsStrategy.update(2, 0.1);
517-
expect(state.position.y).toBeCloseTo(0.4);
522+
RapierPhysicsStrategy.update(2, timestep);
523+
expect(state.position.y).toBeCloseTo(4 * timestep);
518524
expect(strategy.isGrounded).toBe(false);
519525
expect(strategy.motionState.state).toBe('rising');
520526
expect(strategy.motionState.verticalSpeed).toBeCloseTo(4);
521-
expect(strategy.motionState.airborneDuration).toBeCloseTo(0.1);
527+
expect(strategy.motionState.airborneDuration).toBeCloseTo(timestep);
522528

523529
strategy.teleport(Vector3.fromCopy3(3, 2, 1));
524530
expect(state.position.isEqual(Vector3.fromCopy3(3, 2, 1))).toBe(true);

src/foundation/physics/Rapier/RapierPhysicsStrategy.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ test('RapierPhysicsStrategy scopes step participants to the processed engine', a
479479
});
480480

481481
test.each([
482-
{ elapsedTime: 10, expectedTimestep: 1 / 15, expectedParticipantDeltaTime: 10 },
482+
{ elapsedTime: 10, expectedTimestep: 1 / 15, expectedParticipantDeltaTime: 1 / 15 },
483483
{ elapsedTime: Number.NaN, expectedTimestep: 1 / 60, expectedParticipantDeltaTime: 1 / 60 },
484484
])('RapierPhysicsStrategy normalizes an unsafe elapsed time before stepping', async ({
485485
elapsedTime,

src/foundation/physics/Rapier/RapierPhysicsStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ export class RapierPhysicsStrategy implements PhysicsStrategy {
590590

591591
for (const [participant, participantEngine] of RapierPhysicsStrategy.__stepParticipants) {
592592
if (participantEngine === state.engine) {
593-
participant.preStep(safeDeltaTime);
593+
participant.preStep(timestep);
594594
}
595595
}
596596
TriggerComponent._beginPhysicsStep(state.engine);

0 commit comments

Comments
 (0)