Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions games/world-cup/pinocchio/clients/typescript/src/fifa-2026.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -110,19 +111,19 @@ export const SLOT_LABELS: readonly string[] = [
'2L',
'1H',
'2J',
'USA',
'1D',
'3BEFIJ',
'1G',
'3AEHIJ', // slots 8..15
'1C',
'2F',
'2E',
'2I',
'Mexico',
'1A',
'3CEFHI',
'1L',
'3EHIJK', // slots 16..23
'Argentina',
'1J',
'2H',
'2D',
'2G',
Expand Down Expand Up @@ -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);
Expand Down
54 changes: 44 additions & 10 deletions games/world-cup/pinocchio/clients/typescript/src/teams.ts
Original file line number Diff line number Diff line change
@@ -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};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,80 @@ 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<readonly [team: string, seed: string]> = [
['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}`);
});
});
});

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', () => {
Expand Down
10 changes: 10 additions & 0 deletions games/world-cup/pinocchio/webapp/src/components/bracket/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,21 @@ export interface DisplayTeam {
* `SLOT_LABELS`, add the matching code entry here.
*/
const CODES: Readonly<Record<string, string>> = {
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',
Expand All @@ -40,13 +46,17 @@ const CODES: Readonly<Record<string, string>> = {
Morocco: 'MA',
Netherlands: 'NL',
Nigeria: 'NG',
Norway: 'NO',
Paraguay: 'PY',
Portugal: 'PT',
Qatar: 'QA',
'Saudi Arabia': 'SA',
Senegal: 'SN',
'South Africa': 'ZA',
'South Korea': 'KR',
Spain: 'ES',
Sweden: 'SE',
Switzerland: 'CH',
USA: 'US',
Uruguay: 'UY',
};
Expand Down
Loading