From 1327763de7ef43d8301b8ba2bceb4cac87197bd7 Mon Sep 17 00:00:00 2001 From: amilz <85324096+amilz@users.noreply.github.com> Date: Sun, 28 Jun 2026 07:57:36 -0700 Subject: [PATCH] chore: finalize world cup bracket teams --- .../clients/typescript/src/fifa-2026.ts | 22 +++--- .../pinocchio/clients/typescript/src/teams.ts | 54 +++++++++++--- .../typescript/test/bracket-display.test.ts | 4 +- .../clients/typescript/test/fifa-2026.test.ts | 72 +++++++++++++++++-- .../webapp/src/components/bracket/teams.ts | 10 +++ 5 files changed, 134 insertions(+), 28 deletions(-) diff --git a/games/world-cup/pinocchio/clients/typescript/src/fifa-2026.ts b/games/world-cup/pinocchio/clients/typescript/src/fifa-2026.ts index a37a12296..d39dfa88d 100644 --- a/games/world-cup/pinocchio/clients/typescript/src/fifa-2026.ts +++ b/games/world-cup/pinocchio/clients/typescript/src/fifa-2026.ts @@ -91,14 +91,15 @@ export const MATCH_KICKOFFS: readonly string[] = [ ]; /** - * Team slot `0..31` → seeded label. Already-qualified teams (Germany, USA, - * Mexico) are concrete; the rest are group-position placeholders (`1C` = winner - * of Group C, `2F` = runner-up of Group F, `3ABCDF` = third place from one of - * those groups) until the draw fills them. Slot order is bracket order: this is - * what makes the internal tree reproduce FIFA's matchups, so do not reorder. + * Team slot `0..31` → group-position seed label: `1C` = winner of Group C, + * `2F` = runner-up of Group F, `3ABCDF` = best third place from one of those + * groups. Every entry is a positional seed and stays fixed across draws — the + * real nation that lands in each slot lives in `TEAM_NAMES` (teams.ts). Slot + * order is bracket order: this is what makes the internal tree reproduce FIFA's + * matchups, so do not reorder. */ export const SLOT_LABELS: readonly string[] = [ - 'Germany', + '1E', '3ABCDF', '1I', '3CDFGH', @@ -110,7 +111,7 @@ export const SLOT_LABELS: readonly string[] = [ '2L', '1H', '2J', - 'USA', + '1D', '3BEFIJ', '1G', '3AEHIJ', // slots 8..15 @@ -118,11 +119,11 @@ export const SLOT_LABELS: readonly string[] = [ '2F', '2E', '2I', - 'Mexico', + '1A', '3CEFHI', '1L', '3EHIJK', // slots 16..23 - 'Argentina', + '1J', '2H', '2D', '2G', @@ -223,6 +224,9 @@ export function assertFifaScheduleConsistent(): void { if (!unique.has(m)) throw new Error(`missing match M${m}`); } if (new Set(SLOT_LABELS).size !== TEAM_COUNT) throw new Error('slot labels are not unique'); + for (const label of SLOT_LABELS) { + if (!/^[123][A-L]+$/.test(label)) throw new Error(`slot label ${label} is not a group-position seed`); + } for (const [label, feeders] of FIFA_FEEDERS) { const game = gameIndexOf(label); diff --git a/games/world-cup/pinocchio/clients/typescript/src/teams.ts b/games/world-cup/pinocchio/clients/typescript/src/teams.ts index 17b5b5683..d07c3a4c4 100644 --- a/games/world-cup/pinocchio/clients/typescript/src/teams.ts +++ b/games/world-cup/pinocchio/clients/typescript/src/teams.ts @@ -1,18 +1,52 @@ import { TEAM_COUNT } from './bracket.js'; -import { SLOT_LABELS } from './fifa-2026.js'; /** - * Display names for the 32 positional team slots (`0..31`), defaulting to the - * FIFA 2026 seeding: already-qualified teams as country names and the rest as - * group-position placeholders (`1C`, `2F`, `3ABCDF`, …) — see {@link SLOT_LABELS}. + * Display names for the 32 positional team slots (`0..31`), resolved to the real + * FIFA 2026 knockout-stage qualifiers now that the bracket is final. Each entry's + * trailing comment keeps the slot's original seeding id (`1C`, `2F`, `3ABCDF`, … + * from `SLOT_LABELS` in `fifa-2026.ts`) so the mapping back to group positions + * stays auditable. * - * LAUNCH-DAY REPLACEMENT POINT: as the draw fills group positions, this array is - * the single source the webapp swaps in to flow real names through every display - * helper, and nothing else needs to change. Callers that want to override without - * editing this file can pass their own `names` array to the display helpers. The - * slot order is bracket order and must not change. + * LAUNCH-DAY REPLACEMENT POINT: this array is the single source the webapp swaps + * in to flow real names through every display helper, and nothing else needs to + * change. Callers that want to override without editing this file can pass their + * own `names` array to the display helpers. The slot order is bracket order and + * must not change. */ -export const TEAM_NAMES: readonly string[] = SLOT_LABELS; +export const TEAM_NAMES: readonly string[] = [ + 'Germany', // 1E + 'Paraguay', // 3ABCDF + 'France', // 1I + 'Sweden', // 3CDFGH + 'South Africa', // 2A + 'Canada', // 2B + 'Netherlands', // 1F + 'Morocco', // 2C — slots 0..7 + 'Portugal', // 2K + 'Croatia', // 2L + 'Spain', // 1H + 'Austria', // 2J + 'USA', // 1D + 'Bosnia & Herzegovina', // 3BEFIJ + 'Belgium', // 1G + 'Senegal', // 3AEHIJ — slots 8..15 + 'Brazil', // 1C + 'Japan', // 2F + "Côte d'Ivoire", // 2E + 'Norway', // 2I + 'Mexico', // 1A + 'Ecuador', // 3CEFHI + 'England', // 1L + 'DR Congo', // 3EHIJK — slots 16..23 + 'Argentina', // 1J + 'Cape Verde', // 2H + 'Australia', // 2D + 'Egypt', // 2G + 'Switzerland', // 1B + 'Algeria', // 3EFGIJ + 'Colombia', // 1K + 'Ghana', // 3DEIJL — slots 24..31 +]; /** * The display name for a positional team slot. Defaults to {@link TEAM_NAMES}; diff --git a/games/world-cup/pinocchio/clients/typescript/test/bracket-display.test.ts b/games/world-cup/pinocchio/clients/typescript/test/bracket-display.test.ts index ca5b945ea..b472add48 100644 --- a/games/world-cup/pinocchio/clients/typescript/test/bracket-display.test.ts +++ b/games/world-cup/pinocchio/clients/typescript/test/bracket-display.test.ts @@ -6,10 +6,10 @@ import { bracketRows, champion } from '../src/bracket-display.ts'; import { teamName, TEAM_NAMES } from '../src/teams.ts'; describe('teams', () => { - test('TEAM_NAMES has 32 entries defaulting to the FIFA seeding', () => { + test('TEAM_NAMES has 32 entries resolved to the final bracket teams', () => { assert.equal(TEAM_NAMES.length, 32); assert.equal(TEAM_NAMES[0], 'Germany'); - assert.equal(TEAM_NAMES[31], '3DEIJL'); + assert.equal(TEAM_NAMES[31], 'Ghana'); }); test('teamName resolves a slot and honors a custom names override', () => { diff --git a/games/world-cup/pinocchio/clients/typescript/test/fifa-2026.test.ts b/games/world-cup/pinocchio/clients/typescript/test/fifa-2026.test.ts index e95c27cde..ab904094f 100644 --- a/games/world-cup/pinocchio/clients/typescript/test/fifa-2026.test.ts +++ b/games/world-cup/pinocchio/clients/typescript/test/fifa-2026.test.ts @@ -61,14 +61,72 @@ describe('slot labels', () => { assert.equal(new Set(SLOT_LABELS).size, TEAM_COUNT); }); - test('place the already-qualified teams', () => { - assert.equal(SLOT_LABELS[0], 'Germany'); - assert.equal(SLOT_LABELS[12], 'USA'); - assert.equal(SLOT_LABELS[20], 'Mexico'); + test('are all group-position seeds (1=winner, 2=runner-up, 3=best third)', () => { + for (const label of SLOT_LABELS) { + assert.match(label, /^[123][A-L]+$/, `${label} is not a group-position seed`); + } + // group winners the example bracket fills with host/seed nations + assert.equal(SLOT_LABELS[0], '1E'); // Germany + assert.equal(SLOT_LABELS[12], '1D'); // USA (FIFA host position D1) + assert.equal(SLOT_LABELS[20], '1A'); // Mexico (FIFA host position A1) + assert.equal(SLOT_LABELS[24], '1J'); // Argentina + }); + + test('TEAM_NAMES resolves every slot to a distinct real team', () => { + assert.equal(TEAM_NAMES.length, TEAM_COUNT); + assert.equal(new Set(TEAM_NAMES).size, TEAM_COUNT); }); +}); - test('TEAM_NAMES defaults to the FIFA seeding', () => { - assert.deepEqual(TEAM_NAMES, SLOT_LABELS); +describe('final-bracket seeding', () => { + // Each slot's nation and the FIFA group-position seed it occupies, transcribed + // from the final bracket / FIFA regulations Annex (e.g. USA won Group D, so it + // is the `1D` seed). Kept independent of TEAM_NAMES and SLOT_LABELS so a + // mis-edit of either array is caught here. + const EXPECTED: ReadonlyArray = [ + ['Germany', '1E'], + ['Paraguay', '3ABCDF'], + ['France', '1I'], + ['Sweden', '3CDFGH'], + ['South Africa', '2A'], + ['Canada', '2B'], + ['Netherlands', '1F'], + ['Morocco', '2C'], + ['Portugal', '2K'], + ['Croatia', '2L'], + ['Spain', '1H'], + ['Austria', '2J'], + ['USA', '1D'], + ['Bosnia & Herzegovina', '3BEFIJ'], + ['Belgium', '1G'], + ['Senegal', '3AEHIJ'], + ['Brazil', '1C'], + ['Japan', '2F'], + ["Côte d'Ivoire", '2E'], + ['Norway', '2I'], + ['Mexico', '1A'], + ['Ecuador', '3CEFHI'], + ['England', '1L'], + ['DR Congo', '3EHIJK'], + ['Argentina', '1J'], + ['Cape Verde', '2H'], + ['Australia', '2D'], + ['Egypt', '2G'], + ['Switzerland', '1B'], + ['Algeria', '3EFGIJ'], + ['Colombia', '1K'], + ['Ghana', '3DEIJL'], + ]; + + test('the expected table covers all 32 slots', () => { + assert.equal(EXPECTED.length, TEAM_COUNT); + }); + + test('each slot places the right nation in its FIFA seed', () => { + EXPECTED.forEach(([team, seed], slot) => { + assert.equal(TEAM_NAMES[slot], team, `nation at slot ${slot}`); + assert.equal(SLOT_LABELS[slot], seed, `seed at slot ${slot}`); + }); }); }); @@ -76,7 +134,7 @@ describe('contestantsOf', () => { test('Round-of-32 games show their two team slots', () => { const [a, b] = r32Slots(0); assert.deepEqual(contestantsOf(0), [SLOT_LABELS[a], SLOT_LABELS[b]]); - assert.deepEqual(contestantsOf(0), ['Germany', '3ABCDF']); + assert.deepEqual(contestantsOf(0), ['1E', '3ABCDF']); }); test('later rounds show feeder-match winners', () => { diff --git a/games/world-cup/pinocchio/webapp/src/components/bracket/teams.ts b/games/world-cup/pinocchio/webapp/src/components/bracket/teams.ts index 6d3e548f6..a2c8c446c 100644 --- a/games/world-cup/pinocchio/webapp/src/components/bracket/teams.ts +++ b/games/world-cup/pinocchio/webapp/src/components/bracket/teams.ts @@ -17,15 +17,21 @@ export interface DisplayTeam { * `SLOT_LABELS`, add the matching code entry here. */ const CODES: Readonly> = { + Algeria: 'DZ', Argentina: 'AR', Australia: 'AU', + Austria: 'AT', Belgium: 'BE', + 'Bosnia & Herzegovina': 'BA', Brazil: 'BR', Cameroon: 'CM', Canada: 'CA', + 'Cape Verde': 'CV', Colombia: 'CO', 'Costa Rica': 'CR', Croatia: 'HR', + "Côte d'Ivoire": 'CI', + 'DR Congo': 'CD', Denmark: 'DK', Ecuador: 'EC', Egypt: 'EG', @@ -40,6 +46,8 @@ const CODES: Readonly> = { Morocco: 'MA', Netherlands: 'NL', Nigeria: 'NG', + Norway: 'NO', + Paraguay: 'PY', Portugal: 'PT', Qatar: 'QA', 'Saudi Arabia': 'SA', @@ -47,6 +55,8 @@ const CODES: Readonly> = { 'South Africa': 'ZA', 'South Korea': 'KR', Spain: 'ES', + Sweden: 'SE', + Switzerland: 'CH', USA: 'US', Uruguay: 'UY', };