first commmit
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import { PlayerMotionModel } from '@logic/PlayerMotionModel';
|
||||
import { JumpController, PARABOLIC_HORIZONTAL_SPEED, heightToImpulse } from '@logic/JumpController';
|
||||
import {
|
||||
JUMP_HEIGHT_STANDARD,
|
||||
JUMP_HEIGHT_CHARGED,
|
||||
JUMP_HEIGHT_YELLOW,
|
||||
JUMP_PREPARE_DELAY_MS,
|
||||
PlayerColorState,
|
||||
} from '@common/Constants';
|
||||
|
||||
function newPair(color: PlayerColorState = PlayerColorState.Red) {
|
||||
const motion = new PlayerMotionModel({
|
||||
aabb: { x: 0, y: 16, w: 16, h: 32 },
|
||||
platforms: [{ topY: 0, leftX: -500, rightX: 500 }],
|
||||
initialColorState: color,
|
||||
});
|
||||
motion.update(0.016); // settle on ground
|
||||
const jump = new JumpController(motion);
|
||||
return { motion, jump };
|
||||
}
|
||||
|
||||
describe('JumpController — press / release lifecycle (req 2.2, 2.3, 2.4, 2.8)', () => {
|
||||
it('refuses to press when airborne (req 2.4)', () => {
|
||||
const { motion, jump } = newPair();
|
||||
motion.applyJumpImpulse(500); // lift off manually
|
||||
const res = jump.pressJump(0);
|
||||
expect(res.reason).toBe('airborne');
|
||||
expect(jump.isButtonEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('standard-press + quick release → standard jump height', () => {
|
||||
const { jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
const res = jump.releaseJump(100, 'horizontal');
|
||||
expect(res.height).toBe(JUMP_HEIGHT_STANDARD);
|
||||
expect(res.phase).toBe('crouching');
|
||||
});
|
||||
|
||||
it('holding ≥500ms produces the charged jump (req 2.3)', () => {
|
||||
const { jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
const res = jump.releaseJump(600, 'horizontal');
|
||||
expect(res.height).toBe(JUMP_HEIGHT_CHARGED);
|
||||
});
|
||||
|
||||
it('yellow color state uses 300px baseline jump (req 2.2)', () => {
|
||||
const { jump } = newPair(PlayerColorState.Yellow);
|
||||
jump.pressJump(0);
|
||||
const res = jump.releaseJump(50, 'horizontal', PlayerColorState.Yellow);
|
||||
expect(res.height).toBe(JUMP_HEIGHT_YELLOW);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JumpController — parabolic impulse (req 2.5)', () => {
|
||||
it('parabolic_right imparts +PARABOLIC_HORIZONTAL_SPEED', () => {
|
||||
const { jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
const res = jump.releaseJump(100, 'parabolic_right');
|
||||
expect(res.horizontalImpulse).toBe(PARABOLIC_HORIZONTAL_SPEED);
|
||||
});
|
||||
|
||||
it('parabolic_left imparts −PARABOLIC_HORIZONTAL_SPEED', () => {
|
||||
const { jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
const res = jump.releaseJump(100, 'parabolic_left');
|
||||
expect(res.horizontalImpulse).toBe(-PARABOLIC_HORIZONTAL_SPEED);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JumpController — crouch delay + launch + re-enable (req 2.8, 2.4)', () => {
|
||||
it('does not apply impulse until after 150ms crouch delay', () => {
|
||||
const { motion, jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
jump.releaseJump(100, 'horizontal');
|
||||
// Inside the crouch window — still grounded because impulse not applied.
|
||||
jump.tick(200);
|
||||
expect(motion.vy).toBe(0);
|
||||
expect(motion.isGrounded).toBe(true);
|
||||
jump.tick(100 + JUMP_PREPARE_DELAY_MS + 1);
|
||||
expect(motion.vy).toBeGreaterThan(0);
|
||||
expect(motion.isGrounded).toBe(false);
|
||||
});
|
||||
|
||||
it('re-enables the jump button once the player lands again', () => {
|
||||
const { motion, jump } = newPair();
|
||||
jump.pressJump(0);
|
||||
jump.releaseJump(50, 'horizontal');
|
||||
jump.tick(50 + JUMP_PREPARE_DELAY_MS + 1);
|
||||
// Let gravity bring the player back down.
|
||||
for (let i = 0; i < 120; i++) motion.update(1 / 60);
|
||||
jump.tick(1_000);
|
||||
expect(motion.isGrounded).toBe(true);
|
||||
expect(jump.isButtonEnabled()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('heightToImpulse — physics math', () => {
|
||||
it('computes impulse such that peak equals the requested height', () => {
|
||||
const g = 2500;
|
||||
const h = 250;
|
||||
const v0 = heightToImpulse(h, g);
|
||||
// At apex v=0 ⇒ t = v0/g ⇒ peak = v0 * t - 0.5 * g * t^2 ⇒ v0^2 / (2g)
|
||||
expect((v0 * v0) / (2 * g)).toBeCloseTo(h, 3);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user