astra
This commit is contained in:
parent
c074bc2645
commit
244620105a
1 changed files with 2080 additions and 518 deletions
|
|
@ -38,7 +38,7 @@ describe('local prediction reconciliation', () => {
|
|||
auth.rightFoot as never,
|
||||
);
|
||||
|
||||
const used = predictor.update([], 1 / 60);
|
||||
const used = predictor.update([]);
|
||||
|
||||
expect(used).toBe(true);
|
||||
expect(predictor.head.center[0]).toBeCloseTo(auth.head.center[0], 5);
|
||||
|
|
@ -62,7 +62,7 @@ describe('local prediction reconciliation', () => {
|
|||
predictor.recordInput([1, 0]);
|
||||
|
||||
setFrameTimeMs(1100);
|
||||
predictor.update([], 1 / 60);
|
||||
predictor.update([]);
|
||||
return [
|
||||
predictor.head.center[0],
|
||||
predictor.head.center[1],
|
||||
|
|
@ -94,7 +94,7 @@ describe('local prediction reconciliation', () => {
|
|||
predictor.setAlive(false);
|
||||
|
||||
setFrameTimeMs(5200);
|
||||
expect(predictor.update([], 1 / 60)).toBe(false);
|
||||
expect(predictor.update([])).toBe(false);
|
||||
});
|
||||
|
||||
it('replays the whole un-acknowledged span, so a longer trip predicts further', () => {
|
||||
|
|
@ -113,7 +113,7 @@ describe('local prediction reconciliation', () => {
|
|||
auth.rightFoot as never,
|
||||
);
|
||||
|
||||
predictor.update([], 1 / 60);
|
||||
predictor.update([]);
|
||||
return predictor.head.center[0];
|
||||
};
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ describe('local prediction reconciliation', () => {
|
|||
auth.rightFoot as never,
|
||||
);
|
||||
|
||||
predictor.update([], 1 / 60);
|
||||
predictor.update([]);
|
||||
return predictor.head.center[0];
|
||||
};
|
||||
|
||||
|
|
@ -175,7 +175,7 @@ describe('local prediction reconciliation', () => {
|
|||
);
|
||||
|
||||
setFrameTimeMs(40);
|
||||
predictor.update([], 1 / 60);
|
||||
predictor.update([]);
|
||||
return [predictor.head.center[0], predictor.head.center[1]];
|
||||
};
|
||||
|
||||
|
|
@ -189,3 +189,127 @@ describe('local prediction reconciliation', () => {
|
|||
expect(apart).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
|
||||
const settledPoseAt = (x: number) => ({
|
||||
head: { center: [x, 55 - 55 / 3], radius: HEAD_RADIUS },
|
||||
leftFoot: { center: [x - 20, -55 / 3], radius: FEET_RADIUS },
|
||||
rightFoot: { center: [x + 20, -55 / 3], radius: FEET_RADIUS },
|
||||
});
|
||||
|
||||
const install = (predictor: LocalCharacterPredictor, timeMs: number, x: number) => {
|
||||
const pose = settledPoseAt(x);
|
||||
predictor.acknowledge(timeMs, movementState() as never, -Infinity);
|
||||
predictor.setAuthoritative(
|
||||
pose.head as never,
|
||||
pose.leftFoot as never,
|
||||
pose.rightFoot as never,
|
||||
);
|
||||
};
|
||||
|
||||
describe('prediction corrections and interruptions', () => {
|
||||
it('blends an authoritative correction and converges without slowing ordinary motion', () => {
|
||||
const predictor = new LocalCharacterPredictor();
|
||||
setFrameTimeMs(1000);
|
||||
install(predictor, 1000, 0);
|
||||
predictor.update([]);
|
||||
|
||||
setFrameTimeMs(1016);
|
||||
install(predictor, 1016, 100);
|
||||
predictor.update([]);
|
||||
expect(predictor.head.center[0]).toBeCloseTo(0, 5);
|
||||
|
||||
setFrameTimeMs(1032);
|
||||
predictor.update([]);
|
||||
expect(predictor.head.center[0]).toBeGreaterThan(0);
|
||||
expect(predictor.head.center[0]).toBeLessThan(100);
|
||||
|
||||
setFrameTimeMs(1516);
|
||||
predictor.update([]);
|
||||
expect(predictor.head.center[0]).toBeCloseTo(100, 0);
|
||||
});
|
||||
|
||||
it('freezes at the replay horizon during an outage even if held input changes', () => {
|
||||
const predictor = new LocalCharacterPredictor();
|
||||
setFrameTimeMs(0);
|
||||
install(predictor, 0, 0);
|
||||
predictor.recordInput([1, 0] as never);
|
||||
setFrameTimeMs(400);
|
||||
predictor.update([]);
|
||||
const frozen = [...predictor.head.center];
|
||||
|
||||
setFrameTimeMs(450);
|
||||
predictor.recordInput([-1, 0] as never);
|
||||
setFrameTimeMs(600);
|
||||
predictor.update([]);
|
||||
expect([...predictor.head.center]).toEqual(frozen);
|
||||
|
||||
setFrameTimeMs(2000);
|
||||
predictor.recordInput([0, 0] as never);
|
||||
setFrameTimeMs(2500);
|
||||
predictor.update([]);
|
||||
expect([...predictor.head.center]).toEqual(frozen);
|
||||
});
|
||||
|
||||
it('clears visual correction on respawn and disables prediction for end-game snapshots', () => {
|
||||
const predictor = new LocalCharacterPredictor();
|
||||
setFrameTimeMs(1000);
|
||||
install(predictor, 1000, 0);
|
||||
predictor.update([]);
|
||||
install(predictor, 1000, 100);
|
||||
predictor.update([]);
|
||||
predictor.reset();
|
||||
install(predictor, 1000, 1000);
|
||||
predictor.update([]);
|
||||
expect(predictor.head.center[0]).toBe(1000);
|
||||
predictor.enabled = false;
|
||||
expect(predictor.update([])).toBe(false);
|
||||
predictor.reset();
|
||||
install(predictor, 1000, 1000);
|
||||
expect(predictor.update([])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
const ground = {
|
||||
id: 1,
|
||||
vertices: Array.from({ length: 15 }, (_, i) => {
|
||||
const angle = (i / 15) * -Math.PI * 2;
|
||||
return [800 * Math.cos(angle), 800 * Math.sin(angle)];
|
||||
}),
|
||||
snapshotRotation: 0,
|
||||
snapshotRotationSpeed: 0,
|
||||
};
|
||||
|
||||
const leapHeight = (cooldown: number, leap: boolean) => {
|
||||
const predictor = new LocalCharacterPredictor();
|
||||
setFrameTimeMs(1000);
|
||||
const pose = poseAt(0, 900);
|
||||
predictor.acknowledge(
|
||||
1000,
|
||||
{
|
||||
...movementState(),
|
||||
groundPlanetId: 1,
|
||||
secondsSinceOnSurface: 0,
|
||||
leapCooldownRemaining: cooldown,
|
||||
} as never,
|
||||
-Infinity,
|
||||
);
|
||||
predictor.setAuthoritative(
|
||||
pose.head as never,
|
||||
pose.leftFoot as never,
|
||||
pose.rightFoot as never,
|
||||
);
|
||||
if (leap) predictor.recordLeap();
|
||||
setFrameTimeMs(1002);
|
||||
predictor.update([ground] as never);
|
||||
return predictor.head.center[1];
|
||||
};
|
||||
|
||||
describe('leap prediction', () => {
|
||||
it('applies a newly pressed leap in the very next fractional render tick', () => {
|
||||
expect(leapHeight(0, true)).toBeGreaterThan(leapHeight(0, false) + 1);
|
||||
});
|
||||
|
||||
it('does not predict a leap the authoritative cooldown will reject', () => {
|
||||
expect(leapHeight(0.2, true)).toEqual(leapHeight(0.2, false));
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue