# JWE v2 — Décisions d'architecture (Agent Architecte)

Date : 2026-07-16 · Mission : refonte GeoGuessr souverain (voir `projet_geoguessr_97x.md`)

## 1. Décision back-end : SvelteKit seul (pas de Strapi)

**Décision : SvelteKit seul, avec les lieux dans un fichier JSON versionné dans Git.**

Arguments :

- **Frugalité OKI** : un seul processus Node à héberger, pas de CMS + admin + DB à maintenir, pas de surface d'attaque supplémentaire. SQLite serait de toute façon peu utile pour un corpus de ~40 lieux statiques.
- **Critère du prompt** (« qui ajoute les lieux ? ») : le corpus est éditorial, produit par l'Agent Contenu et relu. Le workflow Git (pull request = relecture + crédits photos vérifiés) est la modération. Le `docs/contribution.md` documente l'ajout d'un lieu par PR.
- **Livrables finaux** : le guide de déploiement demandé est « SvelteKit + Tile Server » — Strapi n'y figure pas.
- **Anti-triche** : le JSON reste côté serveur (`src/lib/server/`), jamais importé par le client ; les endpoints filtrent les champs. Impossible de faire plus simple.

Si un jour des contributeurs non-dev doivent saisir des lieux en masse, la porte vers Strapi reste ouverte (le schéma JSON est le content-type tout désigné), mais ce n'est pas le besoin actuel.

## 2. Architecture des tiles MapLibre

- **Rendu** : MapLibre GL JS (verrouillé), chargé en lazy (import dynamique uniquement sur les écrans de jeu / exploration).
- **Dev / démo** : tiles vectorielles [OpenFreeMap](https://openfreemap.org) — libres, basées OpenStreetMap, sans clé API. Style **custom OKI** (JSON de style propre, dérivé du style Positron, re-teinté : mer bleu profond caraïbe, terre sable, végétation verte dense, labels sobres — pas le « bleu Google »).
- **Prod auto-hébergée** : `tileserver-gl` + extraits `.mbtiles` OpenStreetMap des 4 régions (Guadeloupe, Martinique, Guyane, La Réunion — quelques dizaines de Mo au total). Procédure dans `docs/deploiement.md`. Le style OKI accepte l'URL des tiles par variable d'environnement (`PUBLIC_TILES_URL`), bascule sans changement de code.
- **Contrainte géographique** : `maxBounds` = enveloppe union des 4 régions (≈ lon −63…57, lat −23…18) + `minZoom`, donc impossible de scroller en Europe/Amérique du Nord. 4 boutons « saut de région » (GUADELOUPE / MARTINIQUE / GUYANE / RÉUNION) replacent la vue sur la région. Limite connue : l'enveloppe rectangulaire inclut l'Atlantique tropical et le Brésil — inoffensif (aucun indice sur le lieu), documenté.
- Attribution © OpenStreetMap contributors affichée.

## 3. Stratégie Wikipedia / Wikidata

Principe : **rien de stocké en base**, interroge l'API en temps réel avec cache court, côté serveur uniquement.

Endpoint serveur : `GET /api/wiki/[qid]` (ex. `Q3077840` = Fort Delgrès — l'exemple `Q2216838` du prompt initial était erroné, corrigé par l'Agent Contenu)

1. `wbgetentities` sur `www.wikidata.org` → récupère le `sitelink` frwiki (et le libellé fr en fallback).
2. `GET https://fr.wikipedia.org/api/rest_v1/page/summary/{titre}` → `title`, `extract`, `content_urls`, `thumbnail`.
3. Réponse normalisée : `{ qid, title, extract, url, thumbnail, description }`.
4. Pas de sitelink fr ou erreur → `404` : le front bascule sur l'écran **Cas B « Pwen blindé »**.
5. **Cache** : `Map` en mémoire, TTL 24 h (+ `Cache-Control: max-age=3600`). User-Agent explicite (`JWE-OKI/x.y (contact)`) comme demandé par la politique Wikimédia.

## 4. Arborescence du projet

```
JWE/                          (dépôt existant — l'ancien PHP est conservé tel quel)
├── app/                      ← nouvelle application SvelteKit 2 / Svelte 5
│   ├── src/
│   │   ├── lib/
│   │   │   ├── server/
│   │   │   │   ├── lieux.ts        (accès données + sélection aléatoire + scoring)
│   │   │   │   └── data/lieux.json (corpus 40 lieux — serveur uniquement)
│   │   │   ├── components/         (GameMap, PhotoPanel, ResultPanel, ScoreCard…)
│   │   │   ├── i18n/               (fr.ts, gcf.ts, store)
│   │   │   ├── styles/oki.css      (tokens couleurs OKI)
│   │   │   └── utils/              (score.ts, geo.ts)
│   │   ├── routes/
│   │   │   ├── +layout.svelte      (header, switch langue FR/CR)
│   │   │   ├── +page.svelte        (accueil : 4 régions, modes, Jouer)
│   │   │   ├── jeu/[mode]/         (écran de jeu split photo/carte)
│   │   │   ├── explorer/           (mode Aprann : carte pleine page)
│   │   │   └── api/
│   │   │       ├── round/+server.ts      (GET : lieu aléatoire SANITISÉ)
│   │   │       ├── guess/+server.ts      (POST : {placeId, lat, lon, hints, timeMs} → score + vérité)
│   │   │       ├── lieux/+server.ts      (GET : catalogue sanitise pour Explorer)
│   │   │       └── wiki/[qid]/+server.ts (GET : extrait Wikipedia, cache 24 h)
│   │   ├── app.html / app.d.ts
│   │   └── service-worker.ts       (PWA)
│   ├── static/                     (manifest.webmanifest, icônes, photos éventuelles)
│   └── package.json / svelte.config.js / vite.config.ts
├── docs/                         (architecture, déploiement, contribution, audit)
├── content/                      (sources de travail de l'Agent Contenu : crédits photos)
└── projet_geoguessr_97x.md
```

## 5. Spécifications API REST (anti-triche)

| Endpoint | Méthode | Payload | Réponse |
|---|---|---|---|
| `/api/round?region=&mode=&exclude=` | GET | — | `{ id, nom, nom_creole, categorie, difficulte, photo{url,credit,alt}, indices[] }` — **jamais** `coordonnees`, `commune`, `region`, `wikidata_id` |
| `/api/guess` | POST | `{ id, lat, lon, hintsUsed, timeMs? }` | `{ distanceKm, score, coordonnees, commune, region, nom, wikidata_id }` |
| `/api/lieux` | GET | — | Catalogue sanitise (sans coordonnées précises : coords arrondies à 0.05° pour l'affichage Explorer uniquement — mode sans score) |
| `/api/wiki/[qid]` | GET | — | `{ title, extract, url, thumbnail }` ou `404` |

Règles :

- Le scoring est **recalculé côté serveur** : `score = round(5000 × e^(−d/r))`, `r` selon difficulté du lieu (50/15/5/1 km), pénalité −10 %/indice, bonus temps ≤ +20 % plafonné à 5000.
- Distance : haversine.
- Le client ne reçoit la vérité (coordonnées, commune, wikidata_id) **qu'après** la soumission du marqueur.
- `exclude` : liste d'ids déjà joués (mode Défi 5 rounds, stateless — pas de session serveur nécessaire, pas de leaderboard).

## 6. Décisions annexes

- **Adapter** : `adapter-node` (auto-hébergement sobre derrière nginx/caddy).
- **PWA** : manifest + service worker minimal (cache app shell, photos en stale-while-revalidate).
- **i18n** : dictionnaires maison `fr` / `gcf` (créole), store Svelte, `lang` persistée en localStorage.
- **Photos** : URLs Wikimedia Commons (Special:FilePath, `?width=1200`) vérifiées CC par l'Agent Contenu ; `<img loading="lazy">` + `srcset`.
- **A11y / calm tech** : `prefers-reduced-motion` respecté (pas de vol de marqueur), timer informatif non anxiogène, navigation clavier (carte : flèches déplacent le marqueur, Entrée valide).
- **Licence code** : MIT (le dépôt historique était AGPL — la refonte est un nouveau module `app/` sous MIT comme exigé par le prompt ; le `LICENSE` racine reste l'historique, un `app/LICENSE` MIT est ajouté).
