// ════════════════════════════════════════════════════════════════════════════
// UR2D ROSTER — people, contracts & participation history.
//
// ONE PERSON = ONE RECORD (2026-10-02). Every human lives exactly once in UR2D_PEOPLE,
// keyed by a stable operator HANDLE (slug; never a display name). One Discord account per
// person (user rule) → discordId is unique and is checked on load. Name drift is absorbed
// by the record, not by copies:
//     • display    — current Discord name (cosmetic; old ones go to aliases)
//     • sheetName  — default results-sheet string → the join key to RESULTS
//     • discordId  — the forever anchor (the future bot writes it to the sheets)
//     • aliases[]  — every other name ever seen (old Discord names, other sheet strings)
//   Person  { display, sheetName|null, country, discordId|null, carNumber?, aliases[], notes? }
//
// SEASON ROSTERS (UR2D_ROSTER, keyed `${league}:${seasonLabel}`) reference HANDLES only:
//   Team    { discordRoleId, owners:[h], principal:h|null, mains:[h], reserve:h|null, notes? }
//   sheet   { handle: 'string' } — only where that season's sheet spells someone differently
//   notes   { handle: 'text' }   — season-scoped notes
//   independents [h]             — non-team leagues (AL)
//   Stint   { driver:h, team, role, fromRound, toRound|null }  ← THE HISTORY LEDGER
//           role ∈ 'main' | 'signed-reserve' | 'emergency-reserve'. Mid-season change: CLOSE the
//           old stint (set toRound), push a new one. Never rewrite. Empty ledgers seed from signings.
//
// On load each season gets a derived `people` view (global record + that season's sheet
// string; sheetName = null when the person doesn't drive that season) — so getRoster /
// getTeamRoster return the same shape as before. Problems found on load (unknown handles,
// shared Discord IDs, one name pointing at two people) go to UR2D_ROSTER_ISSUES.
// To rename someone: edit ONE line in UR2D_PEOPLE.
// ════════════════════════════════════════════════════════════════════════════

const UR2D_PEOPLE = {
  // ── handle ──   display                 sheetName (results)   country    discordId
  albono:        { display: 'Ben',                  sheetName: 'Albono',             country: 'gb-sct', discordId: '817789746758680578',  carNumber: 86, aliases: [] },
  algfish:       { display: 'Algfish',              sheetName: 'Algfish',            country: 'ca',     discordId: '1445847143381471232',                  aliases: ['Algfish (8SR, SWR)', 'Algfish (8SR, RGT, SWR)'] },
  axis:          { display: 'Axis',                 sheetName: 'Ax1s',               country: 'gb',     discordId: '664877281642151937',  carNumber: 4, aliases: [] },
  az0t:          { display: 'Az0t',                 sheetName: 'Az0t',               country: 'pl',     discordId: '1299078661781192726', carNumber: 407, aliases: [] },
  bituko:        { display: '𝐁𝐢𝐭𝐮𝐤𝐨',         sheetName: 'Bituko',             country: 'es',     discordId: '680493856675856429',  carNumber: 33, aliases: ['Just Bituko'] },
  cigla:         { display: 'cigla',                sheetName: 'cigla',              country: 'hr',     discordId: '1224788296895561843', aliases: [] },
  coemsvel:      { display: 'coemsvel',             sheetName: 'coemsvel',           country: 've',     discordId: '1266746861838925970', aliases: [] },
  cola:          { display: 'cola',                 sheetName: 'cola',               country: 'fi',     discordId: '1068260801493860392', carNumber: 67, aliases: [] },
  corvid:        { display: 'Corvid',               sheetName: 'Corvid',             country: 'au',     discordId: '239979002364821504',  carNumber: 36, aliases: [] },
  dimartino:     { display: 'DiMartino',            sheetName: 'DiMartino',          country: 'fr',     discordId: '1106224241801703577', carNumber: 117, aliases: [] },
  dogamer:       { display: 'Dogamer #7',           sheetName: 'Dogamer',            country: 'es',     discordId: '734751309588070460',  carNumber: 7, aliases: ['Ingeniero Dogamer'] },
  dziura:        { display: 'Kmicic (dziura)',      sheetName: 'dziuraura',          country: 'pl',     discordId: '1083778362008420444', carNumber: 88, aliases: ['Kmicic', 'Big Dziura'] },
  eloksian:      { display: 'eloksian',             sheetName: 'eloksian',           country: 'de',     discordId: '1284786680670453812', carNumber: 37, aliases: [] },
  f1fan:         { display: 'F1fan',                sheetName: 'F1Fan',              country: 'ro',     discordId: '1269594854216826942', carNumber: 87, aliases: [] },
  fen1x:         { display: 'FEN1X',                sheetName: 'FEN1X',              country: 'ua',     discordId: '1113465419773251696', carNumber: 39, aliases: ['FEN1X 39 (ASR^HKB)'] },
  figo:          { display: 'FIGO',                 sheetName: 'figoeira',           country: 'jp',     discordId: '1058563755795034242', carNumber: 99, aliases: ['Davis “Dihtator” Zarins'] },
  finguala:      { display: 'Finguala',             sheetName: 'Finguala',           country: 'tr',     discordId: '1058869289119141958', carNumber: 14, aliases: [] },
  flf_er_unnamed: { display: 'Unnamed (ER)',         sheetName: null,                 country: 'in',     discordId: null,                  aliases: [], notes: 'Unnamed FLF emergency reserve (UL S25), Indian — no Discord ID given.' },
  flih:          { display: 'Lightning McQueen',    sheetName: 'Flyx',               country: 'lv',     discordId: '1457799161142313049', carNumber: 26, aliases: ['Ezekiel Elliott'] },
  gaelprad:      { display: 'Gaelprad',             sheetName: 'gaelprad29',         country: 'fr',     discordId: '920569344222691428',  carNumber: 129, aliases: [] },
  headchefjoe:   { display: 'Head Chef Joe',        sheetName: null,                 country: 'gb',     discordId: '715259194738999406',  aliases: ['G97 Forever'] },
  hetmenneke:    { display: 'Hetmenneke',           sheetName: null,                 country: 'nl',     discordId: '696625877404090439',  aliases: [] },
  hnandi:        { display: 'hnandi',               sheetName: 'hnandi',             country: 'hu',     discordId: '720162151188398092',  carNumber: 25, aliases: [] },
  honoredone:    { display: 'The Honored One',      sheetName: 'The Honored One',    country: null,     discordId: null,                  aliases: [] },
  jakebrad:      { display: 'jakebrad',             sheetName: 'jakebrad',           country: 'gb',     discordId: '1290473898802151444', carNumber: 2, aliases: [] },
  jeruniy:       { display: 'Jeruniy',              sheetName: 'Jeruniy',            country: 'by',     discordId: '474841751178313728',  carNumber: 13, aliases: [] },
  joshrf:        { display: 'Josh RainFumble',      sheetName: 'Glow',               country: 'fr',     discordId: '1270707266487713793', carNumber: 3, aliases: ['Josh RainFumble (10)'] },
  justin:        { display: 'Justin',               sheetName: 'Justin',             country: 'nl',     discordId: '1202618962773418004', carNumber: 28, aliases: [] },
  kado:          { display: 'Kad\'O',               sheetName: 'Kad\'O',             country: 'fr',     discordId: '588381723876917248',  carNumber: 51, aliases: ['HRT'] },
  kalitka:       { display: 'kalitka',              sheetName: 'kalitka',            country: 'ua',     discordId: '1316724173950812160', carNumber: 555, aliases: [] },
  kochinski:     { display: 'Kochinski',            sheetName: 'Kochinski',          country: 'br',     discordId: '690194477138444359',  carNumber: 29, aliases: [] },
  kozeeel:       { display: 'Kozeeel',              sheetName: 'Kozeeel',            country: 'cz',     discordId: '526840648343617548',  aliases: [] },
  kruzthuney:    { display: 'Kruzthuney',           sheetName: 'Crustunet',          country: 'es',     discordId: '1463514834384457871', carNumber: 404, aliases: [] },
  lexus112:      { display: 'Lexus112',             sheetName: 'Lexus112',           country: 'sk',     discordId: '938329652039123004',  carNumber: 112, aliases: [] },
  majumdar:      { display: 'Majumdar #28',         sheetName: null,                 country: 'fr',     discordId: '1292158323445203038', carNumber: 28, aliases: ['Big Maju'] },
  manbeargoat:   { display: 'ManBearGoat',          sheetName: 'ManBearGoat',        country: 'nl',     discordId: '148156072576811008',  carNumber: 6, aliases: [] },
  mathegyl:      { display: 'Mathegyl',             sheetName: 'Mathegyl',           country: 'fr',     discordId: '697474044731326535',  carNumber: 359, aliases: ['[RSR|ASR|MOW] Mathegyl', '[auto|SNR|MOW] Mathegyl'] },
  matimar:       { display: 'MatiMar82',            sheetName: 'MatiMar',            country: 'pl',     discordId: '1062225817897275442', carNumber: null, aliases: [] },
  menig:         { display: 'Menig Guzman🪖',       sheetName: null,                 country: 'no',     discordId: '992795187900321802',  aliases: ['EL Mono Grande🪖'] },
  mika2mika:     { display: 'mika2mika',            sheetName: 'mika2mika',          country: 'et',     discordId: '236640978176442381',  carNumber: 222, aliases: ['Big Meeks'] },
  miport:        { display: 'Miport',               sheetName: 'Miport',             country: null,     discordId: null,                  aliases: [] },
  mludwig:       { display: 'M. Ludwig',            sheetName: 'M. Ludwig',          country: 'us',     discordId: '800378593644904489',  carNumber: 671, aliases: ['Big Lud'] },
  mrkrunk:       { display: 'Mr.Krunk',             sheetName: 'Mr.Krunk',           country: 'ca',     discordId: '392729535411519499',  carNumber: 46, aliases: [] },
  mulletjuche:   { display: 'Mulletjuche',          sheetName: 'sudjuche',           country: 'bg',     discordId: '1149041808064659466', carNumber: 20, aliases: [] },
  kadqwe:        { display: 'kadqwe',               sheetName: 'kadqwe',             country: null,     discordId: null,                  aliases: [], notes: 'New driver AL/JL S23A (user, 2026-10-05). Country + Discord ID unknown.' },
  izanfd10:      { display: 'izanfd10',             sheetName: 'izanfd10',           country: null,     discordId: null,                  aliases: [], notes: 'New driver AL S23A (user, 2026-10-05). Country + Discord ID unknown.' },
  phyrexianhorde: { display: 'PhyrexianHorde',      sheetName: 'PhyrexianHorde',     country: null,     discordId: null,                  aliases: [], notes: 'New driver AL S23A (user, 2026-10-05). Country + Discord ID unknown.' },
  mystic:        { display: 'Mystic',               sheetName: 'Mystic',                 country: 'us',     discordId: '946108410812846101',  carNumber: 171, aliases: [] },
  nikhil:        { display: 'Nikhil',               sheetName: 'Nikhil',             country: 'ca',     discordId: '778310344456142848',  carNumber: 103, aliases: [] },
  nomscar:       { display: 'nomscar',              sheetName: 'Nomscar',            country: 'pt',     discordId: '720751837040345161',  carNumber: 29, aliases: [] },
  nunothe1st:    { display: 'NunoThe1st',           sheetName: null,                 country: 'pt',     discordId: '816674770446974976',  carNumber: 12, aliases: ['NunoThe2nd', 'Nuno'] }, // = UL champion NunoThe2nd (user confirmed 2026-10-05)
  // Historic drivers (not on a current roster) — records exist only so spelling variants between the Mega Tables and the
  // results DB resolve to ONE person (manual input artifacts, user 2026-10-05).
  raghunathan:   { display: 'M. Raghunathan',       sheetName: null,                 country: null,     discordId: null,  aliases: ['Raghunathan'] },
  hyper:         { display: 'hyper',                sheetName: null,                 country: null,     discordId: null,  aliases: ['hyperion'] },
  rhozzim:       { display: 'Rhôzzim',              sheetName: null,                 country: null,     discordId: null,  aliases: ['Rhozzim'] },
  fabio:         { display: 'Fabio',                sheetName: null,                 country: null,     discordId: null,  aliases: ['Fabioggpt'] },
  gamer15:       { display: 'Gamer-15', sheetName: null, country: null, discordId: null, carNumber: 15, aliases: ['Gamer'] },
  theantiamazing: { display: 'TheAntiAmazing', sheetName: null, country: null, discordId: null, aliases: ['AntiNoNo'] },
  sobel:         { display: 'Sobel', sheetName: null, country: null, discordId: null, aliases: ['Sobelli'] },
  spooder918:    { display: 'Spooder918', sheetName: null, country: null, discordId: null, aliases: ['Spooder'] },
  mandries:      { display: 'M. Andries', sheetName: null, country: null, discordId: null, aliases: ['Andries'] },
  lev:           { display: 'Lev', sheetName: null, country: null, discordId: null, aliases: ['L. Davydov'] },
  camirey9888:   { display: 'camirey9888', sheetName: null, country: null, discordId: null, aliases: ['camirey'] },
  emeryaku:      { display: 'Emeryaku', sheetName: null, country: null, discordId: null, aliases: ['Emery'] },
  peter27:       { display: 'Peter27', sheetName: null, country: null, discordId: null, aliases: ['F1Peter'] },
  ozi:           { display: 'Ozi',                  sheetName: 'Ozi',                country: 'pl',     discordId: '1144497424077963365', carNumber: 777, aliases: ['PIGGY Ozi', 'BIG OZI'] },
  peto:          { display: 'John raincall (Peto)', sheetName: 'Peto',               country: 'ge',     discordId: '838828092109226024',  carNumber: 95, aliases: ['John raincall'] },
  piggy:         { display: 'PIGGY',                sheetName: 'PIGGY',              country: 'gb',     discordId: '788888902722650121',  carNumber: 34, aliases: [] },
  playbob:       { display: 'Playbob Cardih',       sheetName: 'Sigmabob',           country: 'it',     discordId: '1313373412067901443', carNumber: 72, aliases: ['Big Brobb'] },
  polak:         { display: 'Polak (8 💙)',         sheetName: 'Polak',              country: 'pl',     discordId: '724605806900674642',  carNumber: 8, aliases: [] },
  pp:            { display: 'pp',                   sheetName: 'pp_kratochvil',      country: 'cz',     discordId: '777838607636889600',  carNumber: 711, aliases: ['ppkrato'] },
  rafita:        { display: 'Rafita67',             sheetName: null,                 country: 'ro',     discordId: '423520149245526017',  carNumber: 93, aliases: [] },
  rikkie:        { display: 'Rikkie',               sheetName: 'RikkieBoy12',        country: 'nl',     discordId: '901891784890789948',  carNumber: 920, aliases: ['RikkieBoy'] },
  scip:          { display: 'Scip',                 sheetName: 'Scip',               country: 'gb',     discordId: '517806908762095618',  carNumber: 19, aliases: [] },
  snowy:         { display: 'Snowy',                sheetName: null,                 country: 'gb',     discordId: '1300131826001514606', aliases: [] },
  soltari:       { display: 'soltari',              sheetName: 'soltari',            country: 'fi',     discordId: '707991098500579339',  carNumber: 48, aliases: [] },
  strudl:        { display: 'Strudl',               sheetName: 'Strudl',             country: 'cz',     discordId: '835572826240843816',  carNumber: 57, aliases: ['Steudl'], notes: 'Only Strudl in the leagues (user, 2026-10-04) — the HL S22 CPR "Steudl" signing is him.' },
  sundaydriver:  { display: 'SundayDriver',         sheetName: 'SundayDriver',       country: 'fi',     discordId: '1274390811060211715', carNumber: 654, aliases: [] },
  timtroade:     { display: 'TimTroade',            sheetName: 'TimTroade',          country: null,     discordId: null,                  aliases: [] },
  toaster64:     { display: 'Toaster64',            sheetName: 'Toaster64',          country: 'gb',     discordId: '833729415665418250',  carNumber: 64, aliases: [] },
  tomatoes:      { display: 'tomatoes',             sheetName: 'tomatoes',           country: 'gb-wls', discordId: '695546862911881216',  carNumber: 27, aliases: ['[27] tomatoes'] },
  urist:         { display: 'Urist',                sheetName: 'Urist',              country: 'pt',     discordId: '218524637359898626',  carNumber: 23, aliases: ['BCR'] },
  vlad0204:      { display: 'vlad0204',             sheetName: null,                 country: 'ro',     discordId: '531919276861292577',  carNumber: 80, aliases: [] },
  youa:          { display: 'youa…',                sheetName: 'youa',               country: 'za',     discordId: '1432375474729910322', carNumber: 74, aliases: [] },
  zentomy:       { display: 'ZenTomy29',            sheetName: 'Fantomy',            country: 'fr',     discordId: '771371383065804831',  carNumber: 5, aliases: ['Big Tomy'] },
  // ── Named 2026-10-02 from the display-name version of the Autumn signings (were placeholder IDs).
  //    sheetName ASSUMED = Discord name until the new workbooks exist — fix one field if the sheet differs.
  bblue:         { display: 'BBlue',                sheetName: 'BBlue',              country: 'hu',     discordId: '1093922345422426283', carNumber: null, aliases: ['BBlue 🇭🇺 15'] },
  bensterio:     { display: 'bensterio',            sheetName: 'bensterio',          country: 'gb',     discordId: '323177625428426756',  aliases: [] },
  cubs:          { display: 'Cubs',                 sheetName: 'Cubs',               country: 'pl',     discordId: '361575602244943872',  aliases: ['Big Cubs'], notes: '⚠ Discord shows "Big Cubs" (server-wide "Big …" nickname) — base name assumed "Cubs".' },
  g4:            { display: 'G4',                   sheetName: 'G4',                 country: 'ar',     discordId: '565652850261360651',  carNumber: 44, aliases: ['G4 (RMC stan)', 'G4 Joan'] },
  ilbiscione:    { display: 'il_biscione',          sheetName: 'il_biscione',        country: 'gb',     discordId: '1538859550474502227', carNumber: 24, aliases: [] },
  jonifu36:      { display: 'JoniFu36',             sheetName: 'JoniFu36',           country: 'sk',     discordId: '810136148071940097',  aliases: ['JoniFu36 (SCP, VLP)'] },
  kiato:         { display: 'KiATO',                sheetName: 'KiATO',              country: 'es',     discordId: '743813171755614218',  carNumber: 787, aliases: [] },
};

const UR2D_ROSTER = {
  // ── UL · S24 ──
  'ul:S24': {
    league: 'ul', season: 'S24',
    notes: {
      zentomy: 'Marked "(Retired)" in the signing message.',
    },
    teams: {
      ONE:   { discordRoleId: '1054278542386528309', owners: ['figo', 'flih', 'menig'], principal: 'ozi', mains: ['figo', 'ozi'], reserve: 'flih' },
      FLF:   { discordRoleId: '802181559976198175', owners: ['nomscar'], principal: 'manbeargoat', mains: ['mludwig', 'kruzthuney'], reserve: 'youa' },
      LSA:   { discordRoleId: '767089225014968341', owners: ['vlad0204'], principal: 'hnandi', mains: ['f1fan', 'zentomy'], reserve: 'hnandi' },
      SGP:   { discordRoleId: '802182185061842945', owners: ['nunothe1st'], principal: null, mains: ['albono', 'rikkie'], reserve: 'toaster64' },
      RMC:   { discordRoleId: '904034336054079498', owners: ['scip', 'jeruniy'], principal: null, mains: ['corvid', 'scip'], reserve: 'jeruniy' },
      SCP:   { discordRoleId: '865712065151107125', owners: ['hetmenneke'], principal: 'headchefjoe', mains: ['nikhil', 'strudl'], reserve: null },
      RKA:   { discordRoleId: '1054290861833338940', owners: ['matimar'], principal: null, mains: ['matimar', 'piggy'], reserve: 'tomatoes' },
      MAY:   { discordRoleId: '859172688660267059', owners: ['kado'], principal: null, mains: ['cola', 'bituko'], reserve: 'kado' },
      LYR:   { discordRoleId: '878979601232171038', owners: ['urist'], principal: null, mains: ['eloksian', 'kalitka'], reserve: 'urist' },
    },
    stints: [],
  },
  // ── JL · S22A (Early Summer 2026) ──
  'jl:S22A': {
    league: 'jl', season: 'S22A',
    notes: {
      dogamer: 'Also KMP / RGT / SPR.',
      lexus112: 'Also Dinoco / VLP / FLF.',
      mrkrunk: 'Also MZM / ZIP.',
      fen1x: 'Also ASR / HKB.',
    },
    teams: {
      SPR:   { discordRoleId: '1402754088848064572', owners: ['mludwig', 'dogamer', 'ozi'], principal: null, mains: ['dziura', 'coemsvel'], reserve: 'playbob' },
      '8SR': { discordRoleId: '1089071323860451388', owners: ['axis'], principal: null, mains: ['axis', 'cigla'], reserve: 'kochinski' },
      ALR:   { discordRoleId: '1483507345148612690', owners: ['polak'], principal: null, mains: ['finguala', 'az0t'], reserve: 'eloksian' },
      DCR:   { discordRoleId: '1450138289528115302', owners: ['rikkie'], principal: 'lexus112', mains: [], reserve: 'lexus112' },
      ZIP:   { discordRoleId: '1384210669225640067', owners: ['albono'], principal: null, mains: ['mrkrunk', 'gaelprad'], reserve: 'jakebrad' },
      TCR:   { discordRoleId: '1521210146460663828', owners: ['mystic'], principal: null, mains: ['kozeeel', 'sundaydriver'], reserve: 'peto' },
      ASR:   { discordRoleId: '1521211267724083230', owners: ['mathegyl'], principal: null, mains: ['mathegyl', 'fen1x'], reserve: null },
    },
    stints: [],
  },
  // ── HL · S22 (Summer 2026) ──
  'hl:S22': {
    league: 'hl', season: 'S22',
    notes: {
      vlad0204: 'Owner LSA + CPR.',
      strudl: 'Signed by both SWR and CPR (CPR message printed "Steudl") — one person; the Round tabs decide his team per round.',
      kado: 'Owner MAY + HRT.',
      dogamer: 'Also KMP / RGT / SPR.',
      lexus112: 'Also Dinoco / VLP / FLF.',
      mrkrunk: 'Also MZM / ZIP.',
      fen1x: 'Also ASR / HKB.',
      rafita: 'Principal, CPR only.',
      snowy: 'Principal (RSR / VSR).',
      majumdar: 'Owner SWR.',
    },
    teams: {
      VLP:   { discordRoleId: '1088017005459820594', owners: ['mulletjuche', 'bituko'], principal: null, mains: ['lexus112', 'bituko'], reserve: null },
      ARR:   { discordRoleId: '1089070800285466676', owners: ['joshrf', 'pp'], principal: null, mains: ['joshrf', 'pp'], reserve: null },
      HKB:   { discordRoleId: '1057016894005194762', owners: ['menig'], principal: 'figo', mains: ['dimartino', 'kalitka'], reserve: 'fen1x' },
      CPR:   { discordRoleId: '1197564752730259566', owners: ['vlad0204'], principal: 'rafita', mains: ['strudl', 'tomatoes'], reserve: null },
      PRO:   { discordRoleId: '1054290864484126741', owners: ['kado'], principal: null, mains: ['mrkrunk', 'az0t'], reserve: 'peto' },
      RGT:   { discordRoleId: '887815875883859999', owners: ['nunothe1st'], principal: 'ozi', mains: ['playbob', 'dogamer'], reserve: 'dziura' },
      RSR:   { discordRoleId: '1089070508932345856', owners: ['mathegyl'], principal: 'snowy', mains: ['cola', 'finguala'], reserve: 'eloksian' },
      SWR:   { discordRoleId: '1416922806339244145', owners: ['majumdar', 'toaster64'], principal: null, mains: ['strudl', 'kozeeel'], reserve: 'nikhil' },
    },
    stints: [],
  },
  // ── UL · S25 (Summer 2026) — MZM = team 9 (resolved by role ID) ──
  'ul:S25': {
    league: 'ul', season: 'S25',
    notes: {
      vlad0204: 'Owner LSA + CPR.',
      albono: 'Co-owner MZM.',
      nikhil: 'SCP main driver in S24; emergency-reserved for SCP in S25.',
      matimar: 'Owner.',
      kado: 'Owner MAY + HRT.',
      dogamer: 'Also KMP / RGT / SPR.',
      mrkrunk: 'Co-owner; also MZM / ZIP.',
      mika2mika: 'Principal.',
      flf_er_unnamed: 'Emergency-reserved FLF’s vacant main seat for at least one round; name not yet provided, nationality confirmed Indian.',
    },
    teams: {
      ONE:   { discordRoleId: '1054278542386528309', owners: ['figo', 'flih', 'menig'], principal: null, mains: ['figo', 'mludwig'], reserve: 'flih' },
      LSA:   { discordRoleId: '767089225014968341', owners: ['vlad0204'], principal: 'hnandi', mains: ['f1fan', 'mulletjuche'], reserve: 'hnandi' },
      FLF:   { discordRoleId: '802181559976198175', owners: ['nomscar'], principal: null, mains: ['lexus112'], reserve: 'playbob' },
      RKA:   { discordRoleId: '1054290861833338940', owners: ['matimar'], principal: null, mains: ['kalitka', 'finguala'], reserve: null },
      MAY:   { discordRoleId: '859172688660267059', owners: ['kado'], principal: null, mains: ['joshrf', 'dimartino'], reserve: null },
      SCP:   { discordRoleId: '865712065151107125', owners: ['hetmenneke'], principal: 'headchefjoe', mains: ['strudl', 'ozi'], reserve: null },
      SGP:   { discordRoleId: '802182185061842945', owners: ['nunothe1st'], principal: null, mains: ['dziura'], reserve: null },
      KMP:   { discordRoleId: '1089070991768031242', owners: ['dogamer'], principal: null, mains: ['cola', 'bituko'], reserve: 'dogamer' },
      MZM:   { discordRoleId: '1054290859262230528', owners: ['albono', 'mrkrunk'], principal: 'mika2mika', mains: ['corvid', 'justin'], reserve: 'soltari' },
    },
    // Emergency-reserve call-ups (rounds not given by the operator — fill fromRound/toRound when known).
    stints: [
      { driver: 'peto', team: 'FLF', role: 'emergency-reserve', fromRound: null, toRound: null },
      { driver: 'az0t', team: 'FLF', role: 'emergency-reserve', fromRound: null, toRound: null },
      { driver: 'flf_er_unnamed', team: 'FLF', role: 'emergency-reserve', fromRound: null, toRound: null },
      { driver: 'nikhil', team: 'SCP', role: 'emergency-reserve', fromRound: null, toRound: null },
    ],
  },
  // ── UL · S26 (Autumn 2026, Retro) — same 9 teams as S25 ──
  'ul:S26': {
    league: 'ul', season: 'S26',
    notes: {
      figo: 'Owner + reserve.',
      vlad0204: 'Owner LSA + CPR.',
      hnandi: 'Principal only this season (was also reserve in S25).',
      zentomy: 'Was "(Retired)" in S24 — back as a MAY main.',
      kado: 'Owner MAY + PRO.',
      dogamer: 'Co-owner KMP only this season.',
      dziura: 'Principal + driver SGP.',
      mrkrunk: 'Co-owner MZM; drives CL PRO + JL AJR.',
      mathegyl: 'Owner ASX (Ariane SpacEx, ex-RKA; MatiMar owned RKA in S24/S25).',
      joshrf: 'KMP reserve AND MAY team principal — confirmed.',
      mika2mika: 'Co-owner MZM (was principal in S25).',
    },
    teams: {
      ONE:   { discordRoleId: '1054278542386528309', owners: ['figo', 'flih', 'menig'], principal: null, mains: ['flih', 'playbob'], reserve: 'figo' },
      LSA:   { discordRoleId: '767089225014968341', owners: ['vlad0204'], principal: 'hnandi', mains: ['f1fan', 'mulletjuche'], reserve: 'jonifu36' },
      MZM:   { discordRoleId: '1054290859262230528', owners: ['mrkrunk', 'mika2mika'], principal: null, mains: ['corvid', 'justin'], reserve: 'youa' },
      SCP:   { discordRoleId: '865712065151107125', owners: ['hetmenneke'], principal: 'headchefjoe', mains: ['ozi', 'mludwig'], reserve: null },
      KMP:   { discordRoleId: '1089070991768031242', owners: ['kiato', 'dogamer'], principal: null, mains: ['kiato', 'cola'], reserve: 'joshrf' },
      FLF:   { discordRoleId: '802181559976198175', owners: ['nomscar'], principal: null, mains: ['manbeargoat', 'nikhil'], reserve: 'toaster64' },
      SGP:   { discordRoleId: '802182185061842945', owners: ['nunothe1st'], principal: 'dziura', mains: ['dziura', 'g4'], reserve: null },
      ASX:   { discordRoleId: '1054290861833338940', owners: ['mathegyl'], principal: null, mains: ['kalitka', 'ilbiscione'], reserve: null, notes: 'Ariane SpacEx — rebrand of RKA (Rykar Auto), same role ID. Signed as "PENDING REBRAND"; renamed 2026-10-04.' },
      MAY:   { discordRoleId: '859172688660267059', owners: ['kado'], principal: 'joshrf', mains: ['zentomy', 'cubs'], reserve: 'jakebrad' },
    },
    stints: [],
  },
  // ── CL · S23 (Autumn 2026, Retro) — ex-HL; league id stays 'hl' ──
  'hl:S23': {
    league: 'hl', season: 'S23',
    notes: {
      figo: 'Principal HKB.',
      ozi: 'Principal RGT.',
      toaster64: 'Co-owner + driver.',
      bituko: 'Co-owner only this season.',
      mulletjuche: 'Co-owner ARR.',
      snowy: 'Principal ASN (ex-RSR); owner JL AJR.',
    },
    teams: {
      RGT:   { discordRoleId: '887815875883859999', owners: ['nunothe1st'], principal: 'ozi', mains: ['algfish'], reserve: null },
      SWR:   { discordRoleId: '1416922806339244145', owners: ['toaster64', 'majumdar'], principal: null, mains: ['toaster64', 'nikhil'], reserve: 'strudl' },
      HKB:   { discordRoleId: '1057016894005194762', owners: ['menig'], principal: 'figo', mains: ['joshrf', 'playbob'], reserve: null },
      ASN:   { discordRoleId: '1089070508932345856', owners: ['mathegyl'], principal: 'snowy', mains: ['dziura', 'kalitka'], reserve: null, notes: 'Andromeda Supernova — re-acronymed RSR → ASN in the S23 workbook (same role ID).' },
      VLP:   { discordRoleId: '1088017005459820594', owners: ['bituko', 'cubs'], principal: null, mains: ['kiato', 'g4'], reserve: 'jakebrad' },
      ARR:   { discordRoleId: '1089070800285466676', owners: ['pp', 'mulletjuche'], principal: null, mains: ['pp', 'cola'], reserve: null },
      PRO:   { discordRoleId: '1054290864484126741', owners: ['kado'], principal: null, mains: ['mrkrunk'], reserve: null },
      CPR:   { discordRoleId: '1197564752730259566', owners: ['vlad0204', 'mika2mika', 'ilbiscione'], principal: 'rafita', mains: ['mika2mika', 'ilbiscione'], reserve: null },
    },
    stints: [],
  },
  // ── JL · S23A (Early Autumn 2026, Retro pt 1) — ZIP gone, BRR new. Message printed Alfa Romeo as "[ARR]" → kept ALR (role ID); ASE = old ASR (same role ID) ──
  'jl:S23A': {
    league: 'jl', season: 'S23A',
    notes: {
      playbob: 'Principal SPR.',
      axis: 'Owner only this season.',
      lexus112: 'Principal + driver DCR.',
      jakebrad: 'Owner + driver BRR.',
      snowy: 'New AJR owner (was Mathegyl).',
    },
    teams: {
      DCR:   { discordRoleId: '1450138289528115302', owners: ['rikkie'], principal: 'lexus112', mains: ['lexus112', 'bensterio'], reserve: 'bblue' },
      ALR:   { discordRoleId: '1483507345148612690', owners: ['polak'], principal: null, mains: ['kalitka', 'ilbiscione'], reserve: null, notes: 'Signing message printed "[ARR]"; role ID is ALR.' },
      SPR:   { discordRoleId: '1402754088848064572', owners: ['mludwig', 'dogamer', 'ozi'], principal: 'playbob', mains: ['joshrf'], reserve: null },
      BRR:   { discordRoleId: '1551510892179361894', owners: ['jakebrad'], principal: null, mains: ['jakebrad', 'albono'], reserve: 'mika2mika' },
      AJR:   { discordRoleId: '1521211267724083230', owners: ['snowy'], principal: null, mains: ['dziura', 'mrkrunk'], reserve: null, notes: 'Andromeda Jaguar Racing (rebrand 2026-10-04; was ASE). Was ASR (Ariane SpacEx Rykar) — same role ID. Renamed Jaguar 2026-10-03 (Ariane name moved to UL RKA).' },
      '8SR': { discordRoleId: '1089071323860451388', owners: ['axis'], principal: null, mains: ['algfish'], reserve: null },
      TCR:   { discordRoleId: '1521210146460663828', owners: ['mystic'], principal: null, mains: ['sundaydriver', 'gaelprad'], reserve: null },
    },
    stints: [],
  },
  // ── AL · S23A (Autumn 2026) — from the R1 STARTING DRIVERS grid ("ALS23AR1"). Not team-based; JL TCR mains SundayDriver + Gaelprad also race AL (allowed) ──
  'al:S23A': {
    league: 'al', season: 'S23A',
    notes: {
      polak: 'Grid renders "Polak (8 )" — emoji dropped; normalizes the same.',
      gaelprad: 'Also JL S23A TCR main (allowed).',
      sundaydriver: 'Also JL S23A TCR main (allowed).',
    },
    teams: {
    },
    // 2026-10-05: grid-string `sheet` overrides removed — the live AL S23A Leaderboard uses bare names (Algfish,
    // FEN1X, gaelprad29), so each person's default sheetName joins; grid strings stay as aliases. Mystic + Ax1s
    // raced R1 (Mystic won) → added. kadqwe / izanfd10 / PhyrexianHorde = NEW drivers (user) → records added.
    independents: ['algfish', 'timtroade', 'sundaydriver', 'miport', 'fen1x', 'gaelprad', 'polak', 'honoredone', 'mystic', 'axis', 'kadqwe', 'izanfd10', 'phyrexianhorde'],
    stints: [],
  },
};

// ── NAME NORMALIZER — fold unicode-styled letters (NFKC) then strip non-alnum.
// '𝐁𝐢𝐭𝐮𝐤𝐨'→'bituko', 'M. Ludwig'→'mludwig', '[27] tomatoes'→'27tomatoes'.
const rosterNorm = (s) => (s || '').normalize('NFKC').toLowerCase().replace(/[^a-z0-9]/g, '');

const UR2D_ROSTER_ISSUES = [];

// Load: seed empty stint ledgers, then derive each season's `people` view.
(function buildRosters() {
  const ids = {};
  for (const h of Object.keys(UR2D_PEOPLE)) {
    const id = UR2D_PEOPLE[h].discordId;
    if (!id) continue;
    if (ids[id]) UR2D_ROSTER_ISSUES.push({ kind: 'shared-discord-id', id, handles: [ids[id], h] });
    else ids[id] = h;
  }
  for (const key of Object.keys(UR2D_ROSTER)) {
    const r = UR2D_ROSTER[key];
    if (!r.stints.length) {
      for (const short of Object.keys(r.teams)) {
        const t = r.teams[short];
        for (const h of t.mains) r.stints.push({ driver: h, team: short, role: 'main', fromRound: 1, toRound: null });
        if (t.reserve) r.stints.push({ driver: t.reserve, team: short, role: 'signed-reserve', fromRound: 1, toRound: null });
      }
    }
    const drivers = new Set([...(r.independents || []), ...r.stints.map((s) => s.driver)]);
    Object.values(r.teams).forEach((t) => [...t.mains, t.reserve].forEach((h) => h && drivers.add(h)));
    const all = new Set(drivers);
    Object.values(r.teams).forEach((t) => [...t.owners, t.principal, ...t.mains, t.reserve].forEach((h) => h && all.add(h)));
    r.people = {};
    for (const h of all) {
      const p = UR2D_PEOPLE[h];
      if (!p) { UR2D_ROSTER_ISSUES.push({ kind: 'unknown-handle', season: key, handle: h }); continue; }
      const sheetName = drivers.has(h) ? ((r.sheet && r.sheet[h]) || p.sheetName) : null;
      r.people[h] = { ...p, sheetName, notes: (r.notes && r.notes[h]) || p.notes };
    }
  }
})();

// ── IDENTITY INDEX — every name form → handle. Tiers, strongest first: handle / discordId,
// sheet strings (default, then per-season), display, aliases. First claim in a tier wins;
// a name claimed by two people is logged to UR2D_ROSTER_ISSUES.
const _personIndex = (() => {
  const tiers = [new Map(), new Map(), new Map(), new Map()];
  const add = (t, name, h) => {
    const n = rosterNorm(name);
    if (!n) return;
    const m = tiers[t];
    if (!m.has(n)) m.set(n, h);
    else if (m.get(n) !== h && !UR2D_ROSTER_ISSUES.some((x) => x.kind === 'name-collision' && x.name === n))
      UR2D_ROSTER_ISSUES.push({ kind: 'name-collision', name: n, handles: [m.get(n), h] });
  };
  for (const [h, p] of Object.entries(UR2D_PEOPLE)) { add(0, h, h); if (p.discordId) add(0, p.discordId, h); add(1, p.sheetName, h); }
  for (const r of Object.values(UR2D_ROSTER)) for (const [h, s] of Object.entries(r.sheet || {})) add(1, s, h);
  for (const [h, p] of Object.entries(UR2D_PEOPLE)) add(2, p.display, h);
  for (const [h, p] of Object.entries(UR2D_PEOPLE)) (p.aliases || []).forEach((a) => add(3, a, h));
  return (name) => { const n = rosterNorm(name); if (!n) return null; for (const m of tiers) if (m.has(n)) return m.get(n); return null; };
})();

function _rosterKey(league, seasonId) {
  if (seasonId) return `${league}:${seasonId}`;
  const cur = (getSeasons(league) || []).find((s) => s.isCurrent);
  return cur ? `${league}:${cur.id}` : null;
}

// Full roster object for a league season (or null).
function getRoster(league, seasonId) {
  return UR2D_ROSTER[_rosterKey(league, seasonId)] || null;
}

// The one record for a handle / Discord ID.
function getPerson(handle) {
  const p = UR2D_PEOPLE[handle];
  return p ? { handle, ...p } : null;
}
function getPersonByDiscordId(id) {
  const h = Object.keys(UR2D_PEOPLE).find((k) => UR2D_PEOPLE[k].discordId === String(id));
  return h ? getPerson(h) : null;
}

// ANY name form (handle, Discord ID, sheet string, display, alias) → { handle, ...person }.
function resolvePerson(anyName) {
  const h = _personIndex(anyName);
  return h ? getPerson(h) : null;
}

// One team's resolved roster: people objects (with handle + all name fields) + role flags.
function getTeamRoster(league, short, seasonId) {
  const r = getRoster(league, seasonId);
  const t = r && r.teams[short];
  if (!t) return null;
  const person = (h) => h ? { handle: h, ...(r.people[h] || {}) } : null;
  return {
    discordRoleId: t.discordRoleId,
    owners: t.owners.map(person),
    principal: person(t.principal),
    mains: t.mains.map(person),
    reserve: person(t.reserve), // null = vacant seat
    notes: t.notes || null,
  };
}

// All stints for a driver (resolved from any name form) across every stored roster.
function getDriverHistory(anyName) {
  const who = resolvePerson(anyName);
  if (!who) return [];
  const out = [];
  for (const r of Object.values(UR2D_ROSTER)) {
    for (const s of r.stints) if (s.driver === who.handle) out.push({ league: r.league, season: r.season, ...s });
  }
  return out;
}

// Every role a person has held, every season: owner / principal / main / reserve / independent.
function getPersonRoles(anyName) {
  const who = resolvePerson(anyName);
  if (!who) return [];
  const h = who.handle, out = [];
  for (const r of Object.values(UR2D_ROSTER)) {
    for (const [short, t] of Object.entries(r.teams)) {
      if (t.owners.includes(h)) out.push({ league: r.league, season: r.season, team: short, role: 'owner' });
      if (t.principal === h) out.push({ league: r.league, season: r.season, team: short, role: 'principal' });
      if (t.mains.includes(h)) out.push({ league: r.league, season: r.season, team: short, role: 'main' });
      if (t.reserve === h) out.push({ league: r.league, season: r.season, team: short, role: 'reserve' });
    }
    if ((r.independents || []).includes(h)) out.push({ league: r.league, season: r.season, team: null, role: 'independent' });
  }
  return out;
}

// Country (flagcdn ISO code) for any name form — feeds FlagSvg. Unknown → 'un' (United Nations flag,
// user rule 2026-10-05: always show the UN flag when a driver's country isn't known).
function getRosterCountry(anyName) {
  const who = resolvePerson(anyName);
  return (who && who.country) || 'un';
}

Object.assign(window, {
  UR2D_PEOPLE, UR2D_ROSTER, UR2D_ROSTER_ISSUES, getRoster, getTeamRoster, getDriverHistory, getRosterCountry,
  resolvePerson, rosterNorm, getPerson, getPersonByDiscordId, getPersonRoles,
});
