This commit is contained in:
Andras Schmelczer 2026-08-22 20:59:46 +01:00
commit 98c4d63756
2 changed files with 155 additions and 223 deletions

View file

@ -69,31 +69,7 @@ describe('projectile broadphase registration', () => {
expect(container.findIntersecting(boxAround([0, 0], 100))).not.toContain(projectile);
});
it('fast-forwards a shot by the sender latency', () => {
const container = new PhysicalContainer();
container.initialize();
const spawn = vec2.fromValues(0, 0);
const make = () =>
new ProjectilePhysical(
vec2.clone(spawn),
20,
40,
CharacterTeam.blue,
vec2.fromValues(3000, 0),
{ team: CharacterTeam.blue } as never,
container,
0,
);
const plain = make();
const compensated = make();
compensated.fastForward(0.1);
expect(compensated.center[0]).toBeGreaterThan(plain.center[0] + 200);
});
it('never fast-forwards further than the cap', () => {
it('spawns a shot just clear of the shooter, not a jump downrange', () => {
const container = new PhysicalContainer();
container.initialize();
const projectile = new ProjectilePhysical(
@ -106,9 +82,8 @@ describe('projectile broadphase registration', () => {
container,
0,
);
projectile.fastForward(10);
const cap = settings.chargeShotSpeedMax * settings.maxProjectileCatchUpSeconds;
expect(projectile.center[0]).toBeLessThanOrEqual(cap + 50);
expect(projectile.center[0]).toBeLessThanOrEqual(15);
});
});

View file

@ -255,6 +255,44 @@ describe('reconnect tokens', () => {
});
});
describe('reconnect while the dropped socket is still live', () => {
// engine.io needs ~45 s to notice a transport drop; the client is back in ~1 s.
it('retires the ghost so the returning player keeps its slot, team and score', () => {
const { server, attach } = startServer({ playerLimit: 1, npcCount: 0 });
const first = attach();
first.fire(TransportEvents.PlayerJoining, { name: 'flaky' });
const token = first.lastPayloadFor(TransportEvents.PlayerJoined) as string;
expect(server.serverInfo.playerCount).toBe(1);
// No 'disconnect' fired: the server still holds the dead socket's player.
const second = attach();
second.fire(TransportEvents.PlayerJoining, { name: 'flaky', reconnectToken: token });
expect(second.lastPayloadFor(TransportEvents.JoinRejected)).toBeUndefined();
expect(second.lastPayloadFor(TransportEvents.PlayerJoined)).toBeTruthy();
expect(server.serverInfo.playerCount).toBe(1);
expect(first.listenerCount(TransportEvents.PlayerToServer)).toBe(0);
});
it('does not hand a slot to an unknown token', () => {
const { server, attach } = startServer({ playerLimit: 1, npcCount: 0 });
attach().fire(TransportEvents.PlayerJoining, { name: 'holder' });
const stranger = attach();
stranger.fire(TransportEvents.PlayerJoining, {
name: 'stranger',
reconnectToken: 'not-a-real-token',
});
expect(stranger.lastPayloadFor(TransportEvents.JoinRejected)).toBe(
JoinRejectionReason.ServerFull,
);
expect(server.serverInfo.playerCount).toBe(1);
});
});
describe('socket listeners', () => {
it('takes every listener back off when the player drops', () => {
const { attach } = startServer({ playerLimit: 4, npcCount: 0 });
@ -266,7 +304,8 @@ describe('socket listeners', () => {
socket.fire('disconnect');
expect(socket.listenerCount(TransportEvents.Pong)).toBe(0);
expect(socket.listenerCount(TransportEvents.PlayerToServer)).toBe(1);
expect(socket.listenerCount(TransportEvents.PlayerToServer)).toBe(0);
expect(socket.listenerCount('disconnect')).toBe(0);
});
it('does not stack a listener per reconnect', () => {