import { Match, Team, GroupTable, ThirdPlaceRow } from "./types"; import { ThirdAssignment, LATER_ROUNDS } from "./bracket"; import { ResolvedSide, ResolvedTie } from "./resolve-bracket"; export type SimOverrides = Record; const STORAGE_KEY = "wm2026-sim-overrides"; export function loadOverrides(): SimOverrides { if (typeof window === "undefined") return {}; try { const raw = localStorage.getItem(STORAGE_KEY); return raw ? JSON.parse(raw) : {}; } catch { return {}; } } export function saveOverrides(overrides: SimOverrides): void { if (typeof window === "undefined") return; try { localStorage.setItem(STORAGE_KEY, JSON.stringify(overrides)); } catch { /* quota exceeded */ } } // Plausibles Standardergebnis aus Polymarket-3-Wege-Wahrscheinlichkeiten. // // Schwellen (zentral): // Draw am höchsten ODER max(home,away) < 0.40 → 0:0 (Gruppe) / Favorit 1:0 (K.o.) // Favorit 0.40–0.60 → 1:0 // Favorit 0.60–0.78 → 2:0 // Favorit > 0.78 → 3:0 // // Beispiele: // Schweiz–Kanada (0.385/0.315/0.295) → 0:0 // Schottland–Brasilien (0.10/0.17/0.75) → 0:2 // Marokko–Haiti (0.83/0.13/0.05) → 3:0 export function defaultScore(match: Match): { homeScore: number; awayScore: number } | null { const prob = match.prob; if (!prob) { if (match.stage === "GROUP") return { homeScore: 1, awayScore: 1 }; return null; } const maxFA = Math.max(prob.home, prob.away); const isDraw = prob.draw > prob.home && prob.draw > prob.away; const isGroup = match.stage === "GROUP"; if (isDraw || maxFA < 0.40) { if (isGroup) return { homeScore: 0, awayScore: 0 }; // K.o.: kein Remis möglich → der wahrscheinlichere von Heim/Auswärts gewinnt knapp, // Draw wird ignoriert (kein gültiges K.o.-Ergebnis). return prob.home >= prob.away ? { homeScore: 1, awayScore: 0 } : { homeScore: 0, awayScore: 1 }; } // Favorit bestimmen const homeFav = prob.home >= prob.away; let goals: number; if (maxFA > 0.78) { goals = 3; } else if (maxFA >= 0.60) { goals = 2; } else { goals = 1; // 0.40–0.60 } if (homeFav) return { homeScore: goals, awayScore: 0 }; return { homeScore: 0, awayScore: goals }; } // Baut die simulierte Match-Liste: gespielte Spiele unverändert, // offene Spiele mit Override oder Polymarket-Default, status = "FINISHED". export function buildSimMatches(realMatches: Match[], overrides: SimOverrides): Match[] { return realMatches.map((m) => { if (m.status === "FINISHED") return m; const override = overrides[m.id]; if (override) { return { ...m, homeScore: override.homeScore, awayScore: override.awayScore, status: "FINISHED" as const }; } const def = defaultScore(m); if (def) { return { ...m, homeScore: def.homeScore, awayScore: def.awayScore, status: "FINISHED" as const }; } return m; }); } // Ermittelt die Gewinner-Seite eines aufgelösten Ties anhand der Scores. function winningSide(tie: ResolvedTie): ResolvedSide | null { if (tie.home.score == null || tie.away.score == null) return null; if (tie.home.score > tie.away.score) return tie.home; if (tie.away.score > tie.home.score) return tie.away; return tie.home; // unentschieden → Heimseite gewinnt } function losingSide(tie: ResolvedTie): ResolvedSide | null { if (tie.home.score == null || tie.away.score == null) return null; if (tie.home.score > tie.away.score) return tie.away; if (tie.away.score > tie.home.score) return tie.home; return tie.away; } // Propagiert Gewinner aus R32 durch alle Folgerunden (R16→QF→SF→Finale). // Füllt teamId/label/code in den späteren Runden mit den Daten des Gewinners. export function propagateSimWinners( r32: ResolvedTie[], later: Record, ): Record { const winners = new Map(); const losers = new Map(); function update(tie: ResolvedTie) { const w = winningSide(tie); const l = losingSide(tie); if (w?.teamId) winners.set(tie.matchNumber, { teamId: w.teamId, label: w.label, code: w.code }); if (l?.teamId) losers.set(tie.matchNumber, { teamId: l.teamId, label: l.label, code: l.code }); } for (const tie of r32) update(tie); const result: Record = {}; for (const km of LATER_ROUNDS) { const tie = later[km.matchNumber]; if (!tie) continue; const src = km.losers ? losers : winners; const hw = src.get(km.fromHome); const aw = src.get(km.fromAway); const home = hw ? { ...tie.home, teamId: hw.teamId, label: hw.label, code: hw.code, isWinner: false, provisional: false } : tie.home; const away = aw ? { ...tie.away, teamId: aw.teamId, label: aw.label, code: aw.code, isWinner: false, provisional: false } : tie.away; const newTie: ResolvedTie = { ...tie, home, away }; update(newTie); result[km.matchNumber] = newTie; } return result; }