Count only completed games in player leaderboards

This commit is contained in:
2026-10-01 12:05:19 -04:00
parent 0cb48c435e
commit 13963cba1c
10 changed files with 83 additions and 29 deletions
+20 -7
View File
@@ -34,7 +34,7 @@ async function matches() {
}>;
}
it('starts counting at round 2, preserves early stats, and excludes players who only stayed for round 1', async () => {
it('publishes round 2 or higher games only after game end, preserving early stats and excluding round 1 departures', async () => {
const { getLeaderboard } = await import('../../src/lib/server/fleet/leaderboard.js');
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
const { serverMatchHistory } = await import('../../src/lib/server/fleet/match-history.js');
@@ -45,14 +45,18 @@ it('starts counting at round 2, preserves early stats, and excludes players who
expect(getPlayerProfile(player().guid)).toBeNull();
expect(serverMatchHistory()).toEqual({ total: 0, matches: [] });
await report('round_change', 2, [player(undefined, { kills: 6, score: 500 })]);
expect(getLeaderboard()).toMatchObject({ total: 1, entries: [expect.objectContaining({
highestRound: 2, kills: 6, bestScore: 1000, roundsSurvived: 1, gamesPlayed: 1, gamesCompleted: 0
})], summary: { players: 1, matches: 1, kills: 6 } });
expect(getLeaderboard()).toMatchObject({ entries: [], maps: [], total: 0, summary: { players: 0, matches: 0, kills: 0 } });
expect(getPlayerProfile(player().guid)).toBeNull();
expect(serverMatchHistory()).toMatchObject({ total: 1, matches: [{ status: 'active' }] });
expect(getPlayerProfile(departed.guid)).toBeNull();
await report('game_end', 2, [player(undefined, { kills: 10 })]);
const board = getLeaderboard();
const profile = getPlayerProfile(player().guid);
const history = serverMatchHistory();
expect(board).toMatchObject({ total: 1, entries: [expect.objectContaining({
highestRound: 2, kills: 10, bestScore: 1000, roundsSurvived: 1, gamesPlayed: 1, gamesCompleted: 1
})], summary: { players: 1, matches: 1, kills: 10 } });
expect(getPlayerProfile(departed.guid)).toBeNull();
expect(history).toMatchObject({ total: 1, matches: [{ startPlayers: 2, uniquePlayers: 2, durationMs: 3000, finalRound: 2 }] });
await report('game_start');
await report('game_end', 1, [player(undefined, { kills: 5000 })]);
@@ -225,12 +229,19 @@ it('credits the highest round without inventing survival before the first midgam
});
it('keeps disconnected player records while completing only the final roster', async () => {
const { getLeaderboard } = await import('../../src/lib/server/fleet/leaderboard.js');
const { getPlayerProfile } = await import('../../src/lib/server/fleet/player-profile.js');
const other = player('4229696', { kills: 40, name: 'Other player' });
await report('game_start', 0, [player(), other]);
await report('round_change', 4, [player(undefined, { kills: 80 }), other]);
expect(getLeaderboard()).toMatchObject({ entries: [], total: 0 });
expect(getPlayerProfile(other.guid)).toBeNull();
await report('game_end', 5, [player(undefined, { kills: 100 })]);
expect(await records(other.guid)).toEqual([expect.objectContaining({ kills: 40, highest_round: 4, rounds_survived: 3, completed: 0 })]);
expect(await records()).toEqual([expect.objectContaining({ kills: 100, highest_round: 5, rounds_survived: 4, completed: 1 })]);
expect(getLeaderboard()).toMatchObject({ total: 2, summary: { matches: 1, players: 2, kills: 140 } });
expect(getPlayerProfile(other.guid)).toMatchObject({ stats: { kills: 40, highestRound: 4,
roundsSurvived: 3, gamesPlayed: 1, gamesCompleted: 0 }, matches: [{ status: 'completed', completed: 0 }] });
});
it('starts separate matches for game starts, map changes, round resets, and new rounds after an end', async () => {
@@ -329,10 +340,12 @@ it('keeps deleted match identities and starts a separate match when the server n
server, server_deleted_at: expect.any(String), status: 'interrupted'
});
expect(history.find(match => match.id !== original.id)).toMatchObject({ server, server_deleted_at: null, status: 'active' });
expect(getPlayerProfile(player().guid)).toBeNull();
await report('game_end', 8, [player(undefined, { kills: 120 })]);
const profile = getPlayerProfile(player().guid)!;
expect(profile.stats.gamesPlayed).toBe(2);
expect(profile.matches.find(match => match.id === original.id)?.server_deleted_at).toEqual(expect.any(String));
expect(profile.stats.kills).toBe(240);
expect(profile.stats).toMatchObject({ gamesPlayed: 1, kills: 120 });
expect(profile.matches).toEqual([expect.objectContaining({ id: history.find(match => match.id !== original.id)!.id,
server_deleted_at: null, status: 'completed' })]);
});
it('does not fabricate players for an empty match', async () => {