Fix all but one team dropping out in a group bugging out the bracket

This commit is contained in:
Kalle
2026-08-08 15:48:16 +03:00
parent 7931dc017c
commit 554c31b30b
2 changed files with 115 additions and 13 deletions

View File

@@ -614,6 +614,106 @@ describe("round robin standings - dropped out teams", () => {
expect(standings.map((s) => s.team.id)).toContain(4);
});
// 5 teams and 2 groups produce a 3-team group and a 2-team group. The
// only opponent of the 2-team group's other team drops out before playing,
// so the surviving team has no non-forfeited matches at all.
const twoTeamGroupDropoutTournament = () => {
let data = createResolved({
type: "round_robin",
seeding: [1, 2, 3, 4, 5],
settings: {
groupCount: 2,
},
});
const matchCountByGroupId = new Map<number, number>();
for (const match of data.match) {
matchCountByGroupId.set(
match.groupId,
(matchCountByGroupId.get(match.groupId) ?? 0) + 1,
);
}
const twoTeamGroupId = [...matchCountByGroupId.entries()].find(
([, count]) => count === 1,
)?.[0];
invariant(
typeof twoTeamGroupId === "number",
"no 2-team group in the fixture",
);
let droppedOutTeamId: number | null = null;
let survivingTeamId: number | null = null;
for (const match of data.match) {
const opponent1Id = match.opponent1!.id as number;
const opponent2Id = match.opponent2!.id as number;
if (match.groupId === twoTeamGroupId) {
// the higher seed no-showed and was dropped by the TO;
// endDroppedTeamMatches closes the match with a winner but no score
droppedOutTeamId = Math.max(opponent1Id, opponent2Id);
survivingTeamId = Math.min(opponent1Id, opponent2Id);
data = Engine.reportResult(data, {
matchId: match.id,
winnerSide:
match.opponent1?.id === survivingTeamId ? "opponent1" : "opponent2",
}).data;
continue;
}
const winnerIsOpp1 = opponent1Id < opponent2Id;
data = Engine.reportResult(data, {
matchId: match.id,
scores: winnerIsOpp1 ? [2, 0] : [0, 2],
winnerSide: winnerIsOpp1 ? "opponent1" : "opponent2",
}).data;
}
invariant(droppedOutTeamId !== null && survivingTeamId !== null);
return {
tournament: testTournament({
ctx: {
settings: {
bracketProgression: [
{
type: "round_robin",
name: "RR",
requiresCheckIn: false,
settings: {},
},
],
},
teams: [1, 2, 3, 4, 5].map((teamId) =>
tournamentCtxTeam(teamId, {
seed: teamId,
droppedOut: teamId === droppedOutTeamId ? 1 : 0,
}),
),
},
data,
}),
survivingTeamId,
};
};
it("includes a team whose every group opponent dropped out in standings", () => {
const { tournament, survivingTeamId } = twoTeamGroupDropoutTournament();
const standings = tournament.bracketByIdx(0)!.standings;
expect(standings.map((s) => s.team.id)).toContain(survivingTeamId);
});
it("reports relevantMatchesFinished=true when a 2-team group ended via drop out", () => {
const { tournament } = twoTeamGroupDropoutTournament();
const { relevantMatchesFinished } = tournament
.bracketByIdx(0)!
.source({ placements: [1] });
expect(relevantMatchesFinished).toBe(true);
});
});
describe("round robin A/B divisions standings", () => {

View File

@@ -180,23 +180,25 @@ export class RoundRobinBracket extends Bracket {
}
};
// every team of the group belongs in the standings even if all of its matches were forfeited by dropped out opponents, dropped out ones are seeded further below
const groupTeamIds = R.unique(
matches
.flatMap((match) => [match.opponent1?.id, match.opponent2?.id])
.filter((id) => typeof id === "number"),
);
for (const teamId of groupTeamIds) {
if (droppedOutWithIncompleteMatches.has(teamId)) continue;
updateTeam({ teamId });
}
for (const match of matches) {
const opp1Id = match.opponent1?.id;
const opp2Id = match.opponent2?.id;
const opponentIds = [opp1Id, opp2Id].filter(
if (!match.winnerSide) continue;
const opponentIds = [match.opponent1?.id, match.opponent2?.id].filter(
(id) => typeof id === "number",
);
if (!match.winnerSide) {
// teams yet to finish a match still belong in the standings, dropped out ones are seeded further below
for (const teamId of opponentIds) {
if (droppedOutWithIncompleteMatches.has(teamId)) continue;
updateTeam({ teamId });
}
continue;
}
if (opponentIds.some((id) => droppedOutWithIncompleteMatches.has(id))) {
continue;
}