first commmit

This commit is contained in:
jakciehan
2026-05-06 08:17:32 +08:00
commit 427a33c55b
269 changed files with 20906 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
import { PlayerMotionModel, IPlatform, DEFAULT_GRAVITY } from '@logic/PlayerMotionModel';
import { MOVE_SPEED, PlayerColorState } from '@common/Constants';
function makeGroundPlatform(): IPlatform {
return { topY: 0, leftX: -1000, rightX: 1000 };
}
function makeModel(color: PlayerColorState = PlayerColorState.Red) {
return new PlayerMotionModel({
aabb: { x: 0, y: 16, w: 16, h: 32 }, // character 16x32 resting on y=0 ground
platforms: [makeGroundPlatform()],
initialColorState: color,
});
}
describe('PlayerMotionModel — horizontal movement (req 2.1, 5.1-5.2)', () => {
it('stands still on initialisation', () => {
const m = makeModel();
m.update(0.016);
expect(m.vx).toBe(0);
expect(m.isGrounded).toBe(true);
});
it('moves at 100 px/s in red state', () => {
const m = makeModel(PlayerColorState.Red);
m.update(0.016); // settle
m.setHorizontalInput(1);
m.update(1);
expect(m.vx).toBe(MOVE_SPEED[PlayerColorState.Red]);
expect(m.aabb.x).toBeCloseTo(100, 1);
});
it('moves at 150 px/s in yellow state', () => {
const m = makeModel(PlayerColorState.Yellow);
m.update(0.016);
m.setHorizontalInput(-1);
m.update(1);
expect(m.vx).toBe(-MOVE_SPEED[PlayerColorState.Yellow]);
});
it('reflects speed immediately when setColorState is called mid-run', () => {
const m = makeModel(PlayerColorState.Red);
m.update(0.016);
m.setHorizontalInput(1);
m.setColorState(PlayerColorState.Yellow);
m.update(1);
expect(m.vx).toBe(150);
});
});
describe('PlayerMotionModel — jump / gravity (req 2.4, 13.4)', () => {
it('applyJumpImpulse is rejected when in the air (req 2.4)', () => {
const m = makeModel();
m.update(0.016);
expect(m.applyJumpImpulse(600)).toBe(true); // first jump succeeds
expect(m.isGrounded).toBe(false);
expect(m.applyJumpImpulse(600)).toBe(false); // second jump in air denied
});
it('gravity pulls the player back to the ground', () => {
const m = makeModel();
m.update(0.016);
m.applyJumpImpulse(600);
// Simulate ~1 second of flight — gravity reclaims the player.
for (let i = 0; i < 120; i++) m.update(1 / 60);
expect(m.isGrounded).toBe(true);
expect(m.vy).toBe(0);
});
it('preserves mid-air vx (起跳定型 — req 13.4)', () => {
const m = makeModel();
m.update(0.016);
// Build horizontal speed + jump.
m.setHorizontalInput(1);
m.update(0.016);
m.applyJumpImpulse(500);
const airVx = m.vx;
// Even if the player now tries to flip direction while airborne, vx stays put.
m.setHorizontalInput(-1);
m.update(0.05);
expect(m.vx).toBe(airVx);
});
it('applyHorizontalImpulse overrides vx (for parabolic jumps, req 2.5)', () => {
const m = makeModel();
m.update(0.016);
m.applyJumpImpulse(600);
m.applyHorizontalImpulse(120);
m.update(0.016);
expect(m.vx).toBe(120);
});
});
describe('PlayerMotionModel — platform switching', () => {
it('setPlatforms clears grounded and re-settles on next update', () => {
const m = makeModel();
m.update(0.016);
expect(m.isGrounded).toBe(true);
m.setPlatforms([{ topY: -500, leftX: -10, rightX: 10 }]);
expect(m.isGrounded).toBe(false);
});
it('gravity constant matches the documented default', () => {
expect(DEFAULT_GRAVITY).toBe(2500);
});
});