# FARAWAY card-scanner — Stato e decisioni

> **Documento storico**, fermo a giugno 2026: racconta le prime scelte (OCR,
> DINOv2, Gemini) poi abbandonate per il riconoscimento ORB sul dispositivo.
> Com'è fatta l'app oggi: `README.md`; app native e store: `docs/app-native.md`
> e `docs/store/`.

**Ultimo aggiornamento:** 15 giugno 2026 (sessione autonoma)

Diario operativo: dove siamo, decisioni, prossimi passi. (Lo studio formale è in `studio-fattibilita.md`.)

---

## 1. Obiettivo
App che da **una foto del tableau di fine partita** (8 Regioni + Santuari) **riconosce le carte e calcola il punteggio** di FARAWAY. Flusso: **Nuova partita → giocatore → 1 foto → punteggio → prossimo → classifica**. PWA prima, poi app native. Gratis per gli utenti.

## 2. Metodologia di riconoscimento (decisa)
Instradata per "la carta ha un numero?":
- **Regioni (1–76, Meteore incluse):** OCR del **numero** (motori pronti: Apple Vision / ML Kit nativo; Tesseract.js/ONNX in browser) → lookup nel DB. Meteore disambiguate dal **marker rosa** (HSV). Zero addestramento.
- **Santuari (53, senza numero):** **matching per embedding DINOv2** contro una galleria di riferimenti. Niente addestramento; carta nuova = +1 immagine.
- Una foto per giocatore → detection/ritaglio carte (CV classica / Apple Vision rectangle) → identifica ognuna → **conferma/correggi** → punteggio.
- L'OCR **non è codice nostro**: è un motore pronto da innestare. DINOv2 e il motore di punteggio sì.

## 3. Stato del codice (✅ fatto in autonomia il 15/6)
| File | Cosa | Stato |
|---|---|---|
| `data/faraway-cards-full.json` | **DB unificato 145 carte** (92 regioni: 68 base+9 esp.1+15 Meteore; 53 santuari), quest normalizzate | ✅ |
| `build-db.js` | genera il DB unificato da `cards.js` + `faraway-meteore.json` | ✅ |
| `pwa/scorer.js` | **motore di punteggio UFFICIALE** unificato (in radice c'era una copia ferma a giugno, rimossa il 08/08: i test leggevano quella): tipi quest none/flat/per/set/soglia/distruggi_santuari; visibilità Meteore (stessa ultima cifra); passaggio finale Santuari; prerequisiti | ✅ |
| `test/validate-official.js` | rivalida **IMG_6527 = 120** col motore ufficiale dal DB | ✅ PASS |
| `test/engine-unit.js` | test unitari: quest Meteora, ordine del conteggio, scarto dei Santuari, scelta migliore | ✅ 15/15 |
| `recognition/dino_match.py` | **pipeline DINOv2** (embedding + nearest-neighbor) + self-test | ✅ self-test 100% |
| `recognition/gallery/` | galleria di riferimento (per ora i 6 santuari di IMG_6527) | seme |
| `poc.html` + `score.js` + `cards.js` | PoC iniziale (riconoscimento via Gemini, scoring base) | invariato (legacy) |
| `data/faraway-meteore.json` | 15 Meteore: quest + notte + Clue confermate | ✅ |

## 4. Validazione sul campo
- **IMG_6527 = 120 punti**, validato **carta-per-carta** col foglio del giocatore: Regioni 107 (identiche) + Santuari 13 (8 per-Clue + 3 per-Città + 2 per-Rifugi). Riprodotto dal motore ufficiale.
- DINOv2: 6 santuari ben separati; 30/30 match su versioni alterate (100%).

## 5. Legenda corretta (errore mio precedente sistemato)
- Biomi (frieze): 🟢 verde=**Fiume**, 🔴 rosso=**Foresta**, 🟡 giallo=**Città**, 🔵 blu=**Deserto**, ⬜ grigio=**Rifugi**. (Avevo invertito verde/blu.)
- Simboli: 🗺️ mappa gialla=**Clue**, 🦌 corna rosse=**Okiko**, 💧 gemma blu=**Uddu**, 🌿 pianta=**Goldlog**, 🌙 luna=**notte**. (Avevo scambiato la Clue per okiko.)
- Un Santuario può portare un simbolo **notte** (conta per "5+ notte").

## 6. Note di design da non perdere
- **Distruggi-santuario** (#21/#15/#38): **lo decide il conto, non l'utente** (deciso 08/08). `scoreBest()` prova ogni combinazione possibile, compreso il non scartare affatto, e tiene quella che fa più punti: sul tableau di prova si andava da 52 a 60 a seconda della carta scartata, e a volte conviene rinunciare ai punti della carta per tenere i Santuari. Il Santuario scartato resta nell'elenco, segnato e a zero, perché toglierlo faceva scalare gli indici delle carte dopo.
- **Ordine del conteggio**: si valuta **da destra**, dall'ultima carta giocata alla prima. Il ciclo partiva da sinistra e sullo scarto toglieva il Santuario alle carte sbagliate. Sui punti l'ordine non cambia nulla, per questo non era mai emerso.
- Per i Santuari conviene **identificare la carta (DINOv2) → leggere la quest dal DB** (che è corretto), non interpretare le icone dalla foto (lì ho sbagliato 2 letture).

## 7. Prossimi passi
1. **(utente)** foto dei **53 Santuari** (1 pulita ciascuno; opz. 3–5 reali per robustezza) → costruisco la galleria DINOv2 completa. Per il test iniziale bastano i 6 di IMG_6527 ri-fotografati singolarmente.
2. Test DINOv2 su **foto reali** (non solo aumentate).
3. Costruire l'**app vera** (PWA): cattura foto → detection/ritaglio → OCR numeri (+marker Meteora) → DINOv2 santuari → conferma/correggi → `scorer.js` → punteggio → multi-giocatore/classifica.
4. (poi) migrazione nativa con OCR on-device.

## 8. Mercato
Esiste **faraway-solver.com** (stessa idea, PWA TF.js con classificatori addestrati). Conferma fattibilità; differenziatore possibile: multi-giocatore + UX + italiano + offline/gratis. Non riusare i loro dati/modelli (IP).

---

## 9. PWA — MVP costruito (15/6)
Cartella **`pwa/`** (statica, deployabile su Vercel). Screenshot in `docs/img/pwa-*.jpeg`.
- **Design**: estetica "atlante di viaggio" — base carta/bianco (come la scatola), 5 colori dei biomi, fregi, hero "cielo stellato" con meteora (tema Under Starry Skies). Font Fraunces + Hanken Grotesk.
- **Home**: elenco partite salvate (localStorage) + "Nuova partita".
- **Partita**: classifica giocatori (ordinata) + "Aggiungi giocatore".
- **Aggiungi giocatore**: nome + foto (cattura) → "Analizza" (Gemini, opzionale, legge i numeri Regione) **oppure** "a mano".
- **Ricostruzione**: ogni carta come chip colorato per bioma, con numero/☄️/giorno-notte, simboli, descrizione quest e **punteggio della singola carta**; tocca una carta → **picker** per cambiarla/rimuoverla; totale + Regioni/Santuari.
- **Motore**: usa `scorer.js` + `cards-full.js` (DB 145). Verificato nel browser (8 regioni IMG_6527 = 45 senza santuari; punteggi per-carta corretti; classifica ok).
- **PWA**: `manifest.webmanifest` + `sw.js` (offline) + icone.
- File: `pwa/index.html`, `app.css`, `app.js`, `scorer.js`, `cards-full.js`, `manifest.webmanifest`, `sw.js`, `icons/`.

## 10. Riconoscimento Santuari — svolta del 15/6 (1° lotto di foto reali)
Ricevute 8 carte Santuario (oggi in `assets/photos/sanctuaries/`, allora IMG_6580–6588; 6587=6588 stessa carta su 2 sfondi).
- **Auto-crop** (`recognition/autocrop.py`): ritaglia+raddrizza benissimo (cv2: distanza-colore dal fondo in Lab + `minAreaRect`). Fallisce solo su **carte grigie su beige** (poco contrasto). → **Regola: carte chiare/grigie su feltro verde, carte colorate su beige.**
- **DINOv2 carta intera NON basta** (`recognition/test_realphotos.py`): i Santuari Rifugi grigi con la stessa illustrazione "a scogliera" stanno a cos.≈0.90–0.95 e si confondono; su una 2ª foto reale (6587) sbaglia carta. small/base/large falliscono uguale (non è potenza). Il discriminante sono le **icone**, troppo piccole per l'embedding globale.
- ✅ **Lettore STRUTTURATO** (`recognition/sanctuary_reader.py`): **bioma (colore) + simboli (template matching)** → **8/8**, reef inclusa. Template puliti in `recognition/templates/` (okiko, clue, goldlog, night) ritagliati dalle carte. Soglie per-simbolo (night 0.75; altri 0.55–0.60). Campioni in `recognition/samples/`.
- **Correzioni lettura (confermate dall'utente):** anello+pallino blu = **Notte** (non Uddu); 6581 fornisce Notte; 6584 = +1 punto per Notte. Okiko (freccia rossa) ok.
- ✅ **Distinzione fornisce/punteggio**: aggiunto template `scorebox` (box-valore bianco+petali) → marca il TIPO. Reader ora dà es. "Rifugi · punteggio: N per clue" vs "fornisce: okiko". Template multi-esemplare (più ritagli per simbolo, si prende il max) → robusto ai cambi di scala.
- **Da fare:** match night color-aware; leggere il VALORE numerico nel box (anche negativi); confermare icona **Uddu** + esempio bioma **Città/giallo**; tabella-firme dei 53 dalle etichette utente.

## 11. Pipeline ritaglio/visualizzazione carte (rifinita 15/6, su standard dell'utente)
L'utente ha fornito `santuario.png` = una carta ritagliata alla perfezione → **schema di default**: **PORTRAIT**, rapporto **1.528** (≈41×63 mm), angoli arrotondati (raggio ~10% lato corto), **sfondo trasparente** anti-aliasato. Il suo canale alpha è salvato in `recognition/card_mask.png` e usato come **maschera-stampo** per tutte.
- `recognition/autocrop.py`: **GrabCut** (robusto su zone chiare) → **4 angoli reali = fit delle 4 RETTE dei bordi + intersezione** (`approxPolyDP` dava angoli imprecisi che "shear-avano" la carta → banda obliqua; il fit delle rette dei lati lunghi, scartando gli angoli arrotondati, è preciso) → **warp prospettico** (raddrizza le foto in prospettiva: niente tavolo sui bordi obliqui, banda orizzontale) → rapporto canonico **imposto** + **auto-orient** PORTRAIT con illustrazione in ALTO (metà a basso dettaglio = pannello → in basso). Uscita uniforme (es. 916×1400).
- `recognition/card_finish.py`: applica `card_mask.png` (ridimensionata) → PNG RGBA trasparente, angoli identici al riferimento. Campioni rifiniti in `recognition/samples_finished/`.
- Le carte sono fisicamente identiche → il rapporto è **forzato uguale per tutte** (richiesta utente). Tutte e 8 verificate; vale anche per le Regioni (stesso orientamento verticale, cfr. foto IMG_6527).

### Limiti attuali / da fare

### Limiti attuali / da fare
- Riconoscimento **Regioni** via Gemini (serve chiave in ⚙). Riconoscimento **Santuari** via foto: ancora da fare (manca galleria DINOv2 → si aggiungono a mano dal picker).
- Niente detection/ritaglio automatico carte dalla foto unica (per ora Gemini legge i numeri; i santuari a mano).
- `distruggi_santuari`: la scelta migliore la calcola il motore, non c'è nessuna UI da aggiungere.
- Le carte sono rese come **chip stilizzati** (no art coperta da copyright); quando estrai loghi/simboli dal PDF, si possono arricchire.

## 12. Permesso Catch Up + DEMO (16/6)
- **Catch Up Games ha autorizzato** l'uso dell'arte ufficiale **a condizione che l'app non guadagni nulla** (no vendita/abbonamento/pubblicità). Hanno fornito i visual del **gioco base** (Dropbox → `assets/official/`: 68 Regioni 70×70 quadrate + 45 Santuari 44×68). Espansioni: le mandano **dopo aver visto una prima versione**.
- **DEMO costruita e testata in locale**. In `pwa/`:
  - Arte ufficiale Regioni mappata (`data/region_image_map.json`, numero→file, 68/68 verificati) → `pwa/img/regions/N.jpg`. Santuari: per ora le **foto rifinite** (`recognition/cards53/`) mappate via `data/sanctuary-numbering.json` → `pwa/img/sanctuaries/S-id.png` (arte ufficiale Santuari scaricata ma NON ancora mappata → upgrade futuro; niente auto-map per non rischiare abbinamenti errati art→quest).
  - `app.js`: `cardImg()` + `tcard()` con immagine vera e punteggio in sovraimpressione, picker con miniature, **partita d'esempio** auto-caricata, **badge gratis/no-ads** + crediti Catch Up/Maxime Morin.
  - **Testata con Playwright** (`python -m http.server 8765` in `pwa/`): home, classifica, ricostruzione (immagini caricano, punteggio 24 calcolato), picker — OK.
  - **Da fare con l'utente:** rivedere il demo, testare **riconoscimento beta** (Gemini, serve chiave + foto base), poi **deploy Vercel** e **inviare email** (`docs/email-catchup-draft.md`) con link + richiesta espansioni.
