# 08 · Integrazione con CGE Online **Stato:** analisi, decisione aperta. Rilevato il 2026-07-31 su `account.czechgames.com`. > **Da quale punto di vista è stata fatta questa analisi.** Paolo è **partecipante**, non moderatore, > dei due tornei TdG esaminati (Bolgia: moderatori Emiliano, henrymerrivale, RudyRunner666, Romendil, > Korn73, Signor_Darcy; Duello: RudyRunner666, Romendil). Quindi di quei tornei si è vista la **vista > giocatore**. La vista da creatore è stata verificata su un torneo bozza creato da Paolo stesso, e > **espone più cose** (vedi §3). Questa distinzione è determinante per le conclusioni. ## 1. Il vincolo di partenza CGE Online è il **motore di gioco**: le partite di *Through the Ages* si giocano lì e i tornei si configurano lì. Questo **non è sostituibile**. Il nostro gestionale non prende il posto di CGE, prende il posto del **file Excel** che gli organizzatori usano oggi come companion (ranking di serie, promozioni/retrocessioni, storico, albo). Ne discende il requisito vero: il nostro gestionale deve **supportare tutte le modalità di gioco configurabili su CGE**, e ricevere da CGE i risultati. ## 2. API pubblica: non esiste - **Nessuna chiamata XHR/JSON.** Ispezionando il traffico di rete di `/tournaments/detail/` si vedono solo documenti HTML, CSS, JS e immagini: le pagine arrivano già renderizzate. - **Stack server-side PHP/Nette** (`nette.ajax.js`, `netteForms.js`): architettura a form e postback, non SPA su API. - **Nessuna documentazione di API pubbliche.** L'unico riferimento ufficiale è la guida tornei del forum: . - **`robots.txt` = `User-agent: * / Disallow: /`**, cioè crawling automatico vietato. Quindi: niente da integrare via API, e **niente scraper server-side** (contro le loro regole, fragile, e ci costringerebbe a custodire credenziali di terzi). Sul forum la cosa è stata chiesta più volte e non ha mai ricevuto risposta: [d/1596 "API for game statistics"](https://forum.czechgames.com/d/1596-api-for-game-statistics) (2021, zero risposte) e [d/2409 "Ability to Export Game Plays"](https://forum.czechgames.com/d/2409-ability-to-export-game-plays) (2023, due richieste, nessuna replica in tre anni). Su GitHub non esiste alcun tool di terze parti da riusare o studiare. ### 2.1 Esiste però un'API privata, concessa caso per caso **TTA Pulse** (ttapulse.com), piattaforma community di matchmaking ELO, ha ottenuto da CGE un accesso programmatico con un flusso di consenso tipo OAuth a scope limitati. Non è una voce di corridoio: c'è una dichiarazione di uno staff CGE sul forum ([d/1470](https://forum.czechgames.com/d/1470-ranking-system-game-searching-tool)), *"we […] cooperated with TTA Pulse to provide you with ways to enjoy this aspect of the game"*, e la FAQ di TTA Pulse descriveva il flusso: *"Your authorization is strictly handled by CGE, and they provide limited access to the information listed on the permissions page"*. Due avvertenze. **TTA Pulse oggi è offline**, quindi non c'è nessuno a cui chiedere come abbia fatto, se non CGE stessa. E l'accesso che aveva serviva a **creare partite e leggerne l'esito**, non a leggere il sistema tornei: che quell'API copra anche classifiche e gironi è una supposizione, non un fatto. Coerente con questo, esiste un namespace `/api/` interno all'app che risponde JSON (`{"status":404}`) ma non espone endpoint raggiungibili senza credenziali. ### 2.2 EULA Oltre al `robots.txt`, l'[EULA](https://www.czechgames.com/privacy/through-the-ages) (1.3.3) vieta di *"intercept, emulate or redirect the communication protocols used by CGE […] or as part of content aggregation networks"*. La clausola colpisce l'intercettazione del protocollo app/server più che lo scraping HTML del portale, ma sommata al divieto di crawling non lascia spazio a dubbi sull'intenzione. Un altro motivo per usare la funzione di export, che invece è prevista da loro. ## 3. Ma esiste un export ufficiale (per chi amministra il torneo) Sulla pagina di dettaglio, **solo il creatore/moderatore** vede un pulsante **`Manage`** che il partecipante non ha. Contiene due voci: | Voce | URL | |---|---| | `Edit` | `/tournaments/edit/` | | **`Download results`** | **`/tournaments/downloadresults/`** | **Questa è la via maestra dell'integrazione.** È una funzione *ufficiale e prevista* di CGE per portarsi via i risultati: nessuno scraping, nessuna ambiguità sui ToS, e un formato presumibilmente più stabile dell'HTML. ### 3.1 Il formato, verificato su file reali (2026-07-31) Gli admin hanno fornito tre export veri, salvati come campioni in [`data/sorgenti/cge-export/`](../data/sorgenti/cge-export/) e da usare come fixture dei test del parser. **È un CSV con separatore `;`, ASCII, una riga di intestazione e nient'altro:** ``` group;user;standing;score;played;expired group 1;heisenberg_97;1;68;10;0 group 1;Sellux;2;63;10;0 ``` | Campo | Significato | |---|---| | `group` | `group N`, **vuoto** se il torneo non ha gironi | | `user` | nick CGE | | `standing` | posizione finale nel girone, densa e univoca `1..N` | | `score` | punti torneo, penalità timeout **già incluse** | | `played` | partite giocate | | `expired` | quante sono andate in timeout | I tre campioni coprono le tre forme che ci servono: | File | Struttura | Corrisponde a | |---|---|---| | `bolgia-6x13.csv` | 6 gironi da 13, 78 giocatori | Bolgia vera | | `duello-16x2.csv` | 16 gironi da 2, 32 giocatori | Duello | | `coda-7-senza-gironi.csv` | nessun girone (`group` vuoto), 7 giocatori | Bolgia Coda | **Verifiche fatte sui dati:** - `standing` è **sempre univoco e denso** dentro il girone, e `score` non cresce mai al crescere della posizione. Cioè **CGE risolve i pareggi da sé** e ci consegna un ordine definitivo: a parità di punti assegna comunque posizioni diverse (es. `luigino71` 32 punti 11°, `henrymerrivale` 32 punti 12°). **Non ci serve ricalcolare lo spareggio per cultura.** - La penalità timeout è già dentro `score`, verificato: `Frenk999` ha 0 punti da piazzamenti, 9 timeout e `score` `-9`. - **85 nick su 89 combaciano già** (confronto case-insensitive) con i player nel nostro DB. I 4 nuovi (`gego`, `Jived`, `Schedar`, `Valotta`) sono persone davvero nuove, non varianti di nomi esistenti. ### 3.2 I due limiti veri dell'export **Contiene solo le classifiche.** Nessuna partita singola: niente avversari, niente punteggio cultura, niente date. Popola il nostro `standing`, **non** `match` e `matchParticipant`. **Non ha metadati.** Il file non dice a quale torneo, stagione o stage appartenga: l'header è la prima riga e poi ci sono solo i dati. L'import dovrà quindi **chiedere all'admin** evento, stagione e fase. ### 3.3 L'export è una fotografia, non un archivio Confermato da Paolo il 2026-07-31: il Duello **è ancora in corso**, quindi quel CSV è lo stato *a oggi*, con partite ancora da giocare. Non è un file incompleto per errore, è semplicemente una istantanea. Lo stesso varrà per qualunque torneo non concluso. **Conseguenza sul design dell'import**, che è più forte della sola idempotenza: ricaricare l'export dello stesso torneo deve **aggiornare** le classifiche già presenti, non affiancarne di nuove. Per un dato evento e una data fase, **l'ultimo import vince**. Serve quindi una chiave di scope (evento + fase + girone + giocatore) su cui fare upsert, e va messo in conto che i numeri di un torneo in corso cambieranno a ogni caricamento. Ne segue anche che gli organizzatori potranno ricaricare quando vogliono per aggiornare la vetrina, senza aspettare la fine del torneo. Resta una piccola stranezza da tenere d'occhio, non bloccante: `played` non sempre coincide con la colonna *Games* della pagina web (11 contro 10 per alcuni giocatori della Coda). Per noi restano autorevoli `standing` e `score`. ## 4. Cosa contiene la pagina (già verificato, vista partecipante) Anche senza export, i dati visibili sono già completi. Utile come **fallback** e come termine di paragone per giudicare l'export. ### 4.1 Struttura CGE modella: **Tournament › Stage › Group › Game**. Nota della guida ufficiale: sono supportati solo formati a gironi; con più stage ci sono più gruppi, e il passaggio dei giocatori tra stage è responsabilità dell'organizzatore. | Torneo | Formato su CGE | |---|---| | **Bolgia Goblin (Coda) V2** (`/detail/4970`) | league, 7 giocatori, `3x` partite a 3 + `4x` a 4, con **selettore di Season** (era alla Season 5) | | **Duello TdG 2026** (`/detail/5918`) | 32 giocatori, **16 stage**, **16 gruppi da 2**, seeding **manuale**, `2x` partite a 2 | > **Scoperta rilevante per il nostro engine:** il *Duello*, che è a eliminazione, su CGE **non** è un > bracket. È una sequenza di **stage con gruppi da 2 e seeding manuale**. Se replichiamo lo stesso > schema, il nostro debito `BRACKET_ELIMINAZIONE` potrebbe non servire affatto: basta il motore a > gironi generico più il seeding manuale per fase. Da valutare in fase di design. ### 4.2 Configurazione (form di creazione, 4 step) **Basic:** Name, Moderators, Visibility, Spectating (running/finished), **Rules** (Digital/Tabletop), **Card set** (Original / Base Game / Expansion / **Random Mix**), **Game speed** (da Super Blitz a Endless), Custom time reserve, **Number of stages** (1…100, `Unlimited (league)`), Rank restrictions, **Access** (Free / On Approval), **Tournament start** (Manual / Automatic / At given time). **Advanced ("Group Settings"):** il cuore, e mappa quasi 1:1 sul nostro engine. - **Tournament format**: `2-player` · **`3+4 player games`** (= Bolgia) · `4-player` · `3-player` · custom - **Each player plays**: N partite a **2 / 3 / 4** giocatori - **Timed out player penalization** (es. `-1 points`) - **Ignore players replaced by AI in scoring** (Yes/No) **Players:** roster con stati `joined`, `invitation sent`, `join request`, `waiting for a slot`, `invitation declined`, `blocked`. ### 4.3 Risultati **Standings:** giocatore, n. partite, conteggio piazzamenti (1°/2°/3°/4°, separati per partite a 3 e a 4), punti. In testata: `Scoring: 3p: 10 - 4 - 0` / `4p: 11 - 6 - 2 - 0`. **Games:** ogni partita con posizione, giocatore, **punteggio cultura**, flag timeout. ``` Bolgia Goblin (Coda) v2 game 15 finished 26 days ago 1. Gabriele 129 2. ElTanque35 121 3. kappakre 85 Bolgia Goblin (Coda) v2 game 16 finished 23 days ago 1. Gabriele 289 2. PaoloAlby 186 Majinbuu TIMED OUT ``` ## 5. Mappatura CGE verso il nostro modello dati | CGE | Noi | Note | |---|---|---| | Tournament | `event` | | | Season (selettore) | `season` | le league CGE hanno già le stagioni | | Stage | `phase` | | | Group | `girone` | | | Game | `match` | | | riga giocatore in un game | `matchParticipant` | posizione + `culturaFinale` + timeout | | `TIMED OUT` | rinuncia / `resign` | il nostro engine la pesa già 0.5 | | Standings | `standing` (cache) | ricalcolabile da noi: utile come **verifica incrociata** | | `Scoring: 3p/4p` | `phaseConfig.tabellaPunti` | | | `Each player plays 3x/4x` | composizione round-robin | il nostro `generateRoundRobin` | | `Time Out Penalization` | penalità / `sanction` | | | `Ignore AI in scoring` | flag bot | noi: il bot occupa il posto ma resta fuori classifica | | stati invito | `eventRegistration` | `richiesta/confermata/in_coda/rifiutata` | ### Cosa CGE **non** dà (e resta compito nostro: è il valore del progetto) - **Ranking persistente di serie** (A/B1/B2/C…/H) e **promozioni/retrocessioni** tra stagioni - **Albo d'oro** e storico pluriennale - **Sanzioni disciplinari** e regolamento TdG - **Calendario e notifiche** all'avvio partita - **Identità**: il nick CGE va agganciato al nostro `player.canonicalNick` (abbiamo già il claim) Su questo la guida ufficiale è esplicita: CGE **non automatizza promozioni e retrocessioni**. Il seeding dello stage successivo si fa a mano, guardando i risultati del precedente, e il metodo di seeding va scelto alla creazione perché *"cannot be changed later"*. Cioè: il lavoro che il nostro gestionale toglie di mezzo è esattamente quello che CGE ha deciso di non fare. Vale la pena saperlo anche per un'altra ragione. Tutte le altre classifiche community di TTA (International Championship, Intermezzo, Premier League, Nations League, le leaderboard 2 giocatori) sono tenute **a mano su fogli di calcolo** dagli organizzatori, esattamente come fa oggi la Tana dei Goblin. Non siamo indietro rispetto alla community: siamo allineati, e con questo progetto passiamo avanti. ## 6. Opzioni di integrazione | # | Approccio | Pro | Contro | |---|---|---|---| | **A** | **Import del file di `Download results`**: il moderatore esporta da CGE e carica il file nel nostro admin | funzione **ufficiale** di CGE, formato più stabile dell'HTML, nessuna ambiguità sui ToS | il formato è **ancora da accertare**; solo i moderatori possono esportare | | **B** | **Incolla-e-importa**: si copia la pagina torneo e si incolla in una textarea del nostro admin | funziona **anche per i partecipanti**, nessuna credenziale, si può fare subito | dipende dal formato HTML, che può cambiare | | **C** | **Bookmarklet**: un click sulla pagina CGE legge il DOM e invia JSON al nostro endpoint | UX migliore | più codice; stessa fragilità di B | | **D** | ~~Scraper server-side con credenziali CGE~~ | nessuno | **escluso**: contro `robots.txt`, fragile, custodia di credenziali altrui | | **E** | **Chiedere a CGE un accesso API**, scrivendo a `tournaments@czechgames.com` e citando il precedente TTA Pulse (§2.1) | sarebbe la soluzione definitiva e autorizzata | tempi lunghi, esito incerto; due richieste analoghe sul forum sono rimaste senza risposta per anni | **Raccomandazione: A, ora confermata sui file veri.** Il CSV copre esattamente il cuore del progetto, cioè le classifiche per girone, che sono l'ingresso di promozioni, retrocessioni, ranking e albo. Non copre il dettaglio partite: se e quando vorremo mostrare al giocatore *"le mie partite"* con avversari e cultura, servirà affiancare **B**. Le due cose non si escludono e non hanno la stessa urgenza. ### La scelta architetturale che ne discende Il formato dell'export impone una decisione che vale la pena rendere esplicita. - **CGE come fonte di verità dei risultati.** Importiamo le classifiche così come sono e non ricalcoliamo nulla. Il nostro engine smette di dover sommare i punti (lo fa già CGE, e i suoi numeri sono quelli che i giocatori vedono) e resta prezioso per il resto: **comporre i gironi** e dare all'admin la formazione da riprodurre su CGE, poi **promozioni, retrocessioni, ranking, albo**. - **Noi replichiamo tutto.** Generiamo il calendario, gli admin inseriscono i risultati partita per partita da noi, e il CSV serve solo come verifica incrociata. Più fedele, molto più lavoro manuale per gli organizzatori, e due fonti che possono divergere. La prima è coerente con l'export disponibile e con il fatto che su CGE si gioca comunque. La seconda ha senso solo se gli organizzatori vogliono davvero il dettaglio partita dentro il portale. **E** non è alternativa ad A: è una richiesta a costo quasi zero da mandare in parallelo, sapendo che molto probabilmente non arriverà risposta. CGE lo ha detto apertamente sul forum, sono un team piccolo e le risorse vanno sui giochi nuovi. Se un giorno rispondessero, tanto di guadagnato. ### Rischi da mettere in conto - **Solo i moderatori possono esportare.** Il flusso va disegnato attorno a loro, non attorno al singolo giocatore. Da chiarire chi, tra gli organizzatori, farà l'import e con che cadenza. - **Matching dei nomi**: i nick CGE non sono ID stabili; rename e omonimie vanno gestiti con una mappatura esplicita rivedibile dall'admin. - **Import idempotente**: reimportare lo stesso file non deve duplicare partite. Serve una chiave derivata (torneo + gruppo + nome partita), non l'affidamento all'ordine. - **Fallire in modo esplicito**: meglio un import rifiutato che dati sbagliati in classifica. ## 7. Prossimo passo Il formato è noto e i campioni sono in repo, quindi l'import si può costruire. Restano due cose da decidere con Paolo e gli organizzatori: 1. **Fonte di verità dei risultati**: CGE oppure noi (vedi §6). Da questo dipende quanto lavoro manuale resta agli organizzatori. 2. **Le domande aperte del §3.3** sugli stage, da girare agli admin. Il parser in sé è banale (CSV con `;`, sei colonne). Il lavoro vero è attorno: chiedere all'admin a quale evento, stagione e fase appartiene il file; agganciare i nick ai `player` gestendo i nuovi e gli alias; e rendere l'import **idempotente**, così che ricaricare lo stesso file non duplichi nulla. ## 8. Sottoprodotti utili trovati sulla pagina del torneo Link **reali** presi dalla pagina (non ricostruiti), candidati per `src/lib/content/community.ts`: - Telegram torneo: `https://t.me/joinchat/DLxp9RBjJZwfAs4a7Y5FDw` - Topic forum TdG: `https://www.goblins.net/forum/threads/campionato-goblin-di-tta-aggiornamento-26-05.106651/` - Il "file excel" linkato dal torneo è **lo stesso workbook Drive** già noto (`1964VC-C-IFZGQV4H6xr3MhASWhIVd1qn`) Da confermare con Paolo prima di pubblicarli in vetrina.