The two Wildcard Round winners were crossed into the Elimination Finals by which game they came out of — the 7v10 winner always met 6th and the 8v9 winner always met 5th. The AFL pairs those games by ladder position, as the classic final eight pairs 5v8 and 6v7: the higher host draws the lower-ranked survivor. So when 10th wins through, 5th should meet 10th, not the 8v9 winner. Placement now resolves both games together. A 7v10 winner is placed as soon as it is decided (7th outranks either possible opponent, 10th is outranked by both), while an 8v9 winner is held until the other game is decided rather than being placed and later moved, so results can be entered in either order. The simulator paired the Elimination Finals the same fixed way, which biased every projection that ran off an undecided Wildcard Round; it now re-seeds too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDbHrCce1UhahbkwKkc7hK
99 lines
4 KiB
TypeScript
99 lines
4 KiB
TypeScript
/**
|
|
* AFL Wildcard Round → Elimination Finals re-seeding.
|
|
*
|
|
* The Wildcard Round is drawn 7 v 10 and 8 v 9, and its two winners fill the open slots
|
|
* in the Elimination Finals opposite the 5th and 6th seeds. Those slots are NOT a fixed
|
|
* crossover: the winners are re-seeded by ladder position, exactly as the classic final
|
|
* eight pairs 5 v 8 and 6 v 7 — the higher seed of the two hosts meets the lower-ranked
|
|
* winner. So 5th plays whichever winner finished further down the ladder and 6th plays
|
|
* the other, whichever Wildcard game each came out of.
|
|
*
|
|
* Worked example: 10th beats 7th and 9th beats 8th. A fixed crossover would send the
|
|
* 7v10 winner (10th) to 6th and the 8v9 winner (9th) to 5th — handing the higher host
|
|
* the better opponent. Re-seeded, 5th plays 10th and 6th plays 9th.
|
|
*/
|
|
|
|
/** Seeds drawn into each Wildcard Round match, in [participant1, participant2] order. */
|
|
export const AFL_WILDCARD_DRAW: Readonly<Record<number, readonly [number, number]>> = {
|
|
1: [7, 10],
|
|
2: [8, 9],
|
|
};
|
|
|
|
/** Seed hosting each Elimination Finals match (its participant1 slot). */
|
|
export const AFL_ELIMINATION_HOSTS: Readonly<Record<number, number>> = {
|
|
1: 5,
|
|
2: 6,
|
|
};
|
|
|
|
export interface AflWildcardResult {
|
|
matchNumber: number;
|
|
/** Slot the winner occupied, or null while the match is still to be played. */
|
|
winnerSlot: 1 | 2 | null;
|
|
}
|
|
|
|
export interface AflWildcardPlacement {
|
|
wildcardMatchNumber: number;
|
|
/** Seed of the Wildcard winner being placed. */
|
|
seed: number;
|
|
eliminationMatchNumber: number;
|
|
}
|
|
|
|
/**
|
|
* Decide which Elimination Final each decided Wildcard winner belongs in.
|
|
*
|
|
* A winner is only placed once its destination is settled whichever way the other
|
|
* Wildcard game falls, so results can be entered in either order:
|
|
* - 7th winning match 1 outranks both possible match 2 winners → always meets 6th.
|
|
* - 10th winning match 1 is outranked by both → always meets 5th.
|
|
* - A match 2 winner (8th or 9th) sits between them, so it is held back until match 1
|
|
* is decided rather than being placed and then moved.
|
|
*
|
|
* Undecided winners are simply omitted; the caller fills the slots it is handed and
|
|
* leaves the rest TBD.
|
|
*/
|
|
export function resolveAflWildcardPlacements(
|
|
results: readonly AflWildcardResult[]
|
|
): AflWildcardPlacement[] {
|
|
const entries = results.map((result) => {
|
|
const draw = AFL_WILDCARD_DRAW[result.matchNumber];
|
|
if (!draw) {
|
|
throw new Error(`Unknown AFL Wildcard Round match number ${result.matchNumber}`);
|
|
}
|
|
return {
|
|
matchNumber: result.matchNumber,
|
|
seed: result.winnerSlot === null ? null : draw[result.winnerSlot - 1],
|
|
// Every seed the match could still send through — one entry once it is decided.
|
|
possibleSeeds: result.winnerSlot === null ? [...draw] : [draw[result.winnerSlot - 1]],
|
|
};
|
|
});
|
|
|
|
// Best-ranked winner takes the weakest host, so order the hosts worst seed first.
|
|
const hostsWorstFirst = Object.keys(AFL_ELIMINATION_HOSTS)
|
|
.map(Number)
|
|
.toSorted((a, b) => AFL_ELIMINATION_HOSTS[b] - AFL_ELIMINATION_HOSTS[a]);
|
|
|
|
const placements: AflWildcardPlacement[] = [];
|
|
|
|
for (const entry of entries) {
|
|
const seed = entry.seed;
|
|
if (seed === null) continue;
|
|
|
|
const others = entries.filter((other) => other !== entry);
|
|
const outranks = (other: (typeof entries)[number]) => other.possibleSeeds.every((s) => s < seed);
|
|
const outrankedBy = (other: (typeof entries)[number]) => other.possibleSeeds.every((s) => s > seed);
|
|
|
|
// This winner's rank is only knowable while every other one sits wholly above or
|
|
// wholly below it — an undecided game straddling this seed leaves it unplaceable.
|
|
if (!others.every((other) => outranks(other) || outrankedBy(other))) continue;
|
|
|
|
const rank = others.filter(outranks).length;
|
|
const eliminationMatchNumber = hostsWorstFirst[rank];
|
|
if (eliminationMatchNumber === undefined) {
|
|
throw new Error(`No Elimination Finals slot for AFL Wildcard winner ranked ${rank + 1}`);
|
|
}
|
|
|
|
placements.push({ wildcardMatchNumber: entry.matchNumber, seed, eliminationMatchNumber });
|
|
}
|
|
|
|
return placements;
|
|
}
|