3 Commits

7 changed files with 40 additions and 221 deletions
-1
View File
@@ -33,7 +33,6 @@ npm run build && node build # production (adapter-node)
- [`docs/deploiement.md`](docs/deploiement.md) — auto-hĂ©bergement (SvelteKit + tileserver-gl).
- [`docs/contribution.md`](docs/contribution.md) — ajouter un lieu et lier sa fiche Wikidata.
- [`docs/audit-oki.md`](docs/audit-oki.md) — audit qualitĂ© et doctrine OKI.
- [`prompts/`](prompts/) — prompts de mission et d'audit ayant produit cette refonte.
## Contributions
+34 -27
View File
@@ -23,6 +23,7 @@
interface Props {
guess?: Coordonnees | null;
real?: Coordonnees | null;
distanceKm?: number | null;
interactive?: boolean;
pairs?: Paire[];
markers?: MarqueurInfo[];
@@ -34,6 +35,7 @@
let {
guess = $bindable(null),
real = null,
distanceKm = null,
interactive = true,
pairs = [],
markers = [],
@@ -48,7 +50,6 @@
let guessMarker: Marker | null = null;
let realMarker: Marker | null = null;
let explorerMarkers: Marker[] = [];
let raf = 0;
const SRC_LIGNE = 'jwe-ligne';
const SRC_PAIRES = 'jwe-paires';
@@ -100,39 +101,47 @@
id: 'jwe-ligne-line',
type: 'line',
source: SRC_LIGNE,
filter: ['==', ['geometry-type'], 'LineString'],
paint: { 'line-color': '#e4572e', 'line-width': 3, 'line-dasharray': [2, 1.5] }
});
map.addLayer({
id: 'jwe-ligne-label',
type: 'symbol',
source: SRC_LIGNE,
filter: ['==', ['geometry-type'], 'Point'],
layout: {
'text-field': ['get', 'etiquette'],
'text-size': 14,
'text-font': ['Noto Sans Regular'],
'text-offset': [0, -1.4]
},
paint: { 'text-color': '#0b3d2e', 'text-halo-color': '#fffdf8', 'text-halo-width': 2 }
});
}
const features: GeoJSON.Feature[] = [];
if (guess) {
features.push(ligneFeature(guess, real, null));
if (distanceKm !== null) {
// Étiquette de distance au milieu de la ligne guess ↔ rĂ©el
features.push(
pointFeature(
{ lat: (guess.lat + real.lat) / 2, lon: (guess.lon + real.lon) / 2 },
{ etiquette: `${distanceKm} km` }
)
);
}
}
const features: GeoJSON.Feature[] = guess ? [ligneFeature(guess, real, null)] : [];
setGeoJSON(SRC_LIGNE, { type: 'FeatureCollection', features });
// Marqueur vert = position réelle
// Marqueur vert = position réelle ; le marqueur corail du joueur RESTE
// Ă  sa position : la comparaison visuelle guess ↔ rĂ©el est le cƓur du jeu.
realMarker?.remove();
realMarker = new ml.Marker({ color: '#0b3d2e' }).setLngLat([real.lon, real.lat]).addTo(map);
// Le marqueur du joueur « vole » vers la position réelle (sauf reduced-motion)
if (guessMarker && guess) {
const from = { ...guess };
if (reducedMotion) {
guessMarker.setLngLat([real.lon, real.lat]);
} else {
const debut = performance.now();
const duree = 1400;
const step = (now: number) => {
if (!guessMarker) return;
const k = Math.min(1, (now - debut) / duree);
const e = 1 - Math.pow(1 - k, 3);
guessMarker.setLngLat([
from.lon + (real.lon - from.lon) * e,
from.lat + (real.lat - from.lat) * e
]);
if (k < 1) raf = requestAnimationFrame(step);
};
raf = requestAnimationFrame(step);
}
if (guess) {
const bounds = new ml.LngLatBounds(
[Math.min(from.lon, real.lon), Math.min(from.lat, real.lat)],
[Math.max(from.lon, real.lon), Math.max(from.lat, real.lat)]
[Math.min(guess.lon, real.lon), Math.min(guess.lat, real.lat)],
[Math.max(guess.lon, real.lon), Math.max(guess.lat, real.lat)]
);
map.fitBounds(bounds, { padding: 80, duration: reducedMotion ? 0 : 900, maxZoom: 12 });
} else {
@@ -141,7 +150,6 @@
}
function nettoyerRevelation(): void {
cancelAnimationFrame(raf);
realMarker?.remove();
realMarker = null;
setGeoJSON(SRC_LIGNE, { type: 'FeatureCollection', features: [] });
@@ -311,7 +319,6 @@
})();
return () => {
detruit = true;
cancelAnimationFrame(raf);
map?.remove();
map = null;
};
+1
View File
@@ -266,6 +266,7 @@
<GameMap
bind:guess
real={result ? result.coordonnees : null}
distanceKm={result ? result.distanceKm : null}
interactive={!result}
onvalidate={() => void soumettre(false)}
/>
+1
View File
@@ -260,6 +260,7 @@
<GameMap
bind:guess
real={result ? result.coordonnees : null}
distanceKm={result ? result.distanceKm : null}
interactive={!result}
onvalidate={() => void soumettre(false)}
/>
+4
View File
@@ -106,3 +106,7 @@ RĂšgles :
- **ADR-001 v1 — DĂ©fi par lien Ă  seed** (`/defi/<seed>`) : `src/lib/server/tirage.ts` (hash cyrb53 → PRNG mulberry32, Fisher-Yates dĂ©terministe, contraintes ≄1 MONUMENT / ≄1 LIEU / ≄2 rĂ©gions par Ă©changes dĂ©terministes — zĂ©ro `Math.random`, dĂ©terminisme inter-machines). `GET /api/defi/[seed]` → 5 rounds sanitisés identiques pour tous (`no-store`, seed alphanumĂ©rique 4–40). Accueil : « CrĂ©er un lien de dĂ©fi » (seed 8 chars sans ambiguĂŻtĂ©). Bilan : « Copier le lien du dĂ©fi ». La comparaison des scores reste humaine (image canvas) — zĂ©ro compte, zĂ©ro Ă©tat serveur, zĂ©ro leaderboard. La v2 temps rĂ©el (PlaySocketJS) reste conditionnĂ©e au gate de traction de l'ADR (≄30 parties/sem. ×4 sem. + ≄5 demandes explicites).
- **ADR-002 — Carte SVG lĂ©gĂšre hors-jeu** : pipeline `app/scripts/build-carte-regions.mjs` (geo.api.gouv.fr → mapshaper 7 % → `src/lib/map/regions-geo.json`, 91,8 KB gz ≀ 150 KB) ; composant `CarteRegions.svelte` from scratch (SVG, projection Ă©quirectangulaire par inset, 2×2 responsive, `<button>` par rĂ©gion avec `aria-label` FR/crĂ©ole, SSR sans JS, prop `marqueurs` pour le rĂ©cap guess→rĂ©el, attribution © OSM/donnĂ©es publiques). UtilisĂ©e sur l'accueil (sĂ©lection de rĂ©gion) et les bilans dĂ©fi — MapLibre reste strictement aux Ă©crans de jeu (devinette, rĂ©sultat, explorer). Attribution : contours © geo.api.gouv.fr / donnĂ©es publiques.
## 9. Correctif UX résultat (2026-07-17)
- **Carte résultat façon GeoGuessr** : à la révélation, le marqueur du joueur **reste à sa position** (corail) et la position réelle apparaßt (vert), reliées par une ligne pointillée avec **étiquette de distance au milieu** (`distanceKm`) ; la carte cadre les deux points (`fitBounds`, instantané si `prefers-reduced-motion`). Remplace l'ancien « vol » du marqueur vers la position réelle, qui détruisait la comparaison visuelle. Prop `distanceKm` ajoutée à `GameMap` (alimentée par la réponse serveur, jamais calculée cÎté client).
@@ -1,112 +0,0 @@
# JWE — ADR-001 & ADR-002 (format audit OKI)
Deux décisions d'architecture rédigées selon le format du prompt d'audit post-exécution : critÚres bloquants, preuves exigées, rapport tabulaire, interdits. Elles **ne modifient pas** le build v1 en cours ; elles cadrent la phase 2 et une optimisation perf.
---
# ADR-001 — Mode DĂ©fi : seed-link en v1, PlaySocketJS en phase 2 conditionnĂ©e
- **Statut** : AcceptĂ© (v1) / À Ă©tudier (v2, gate explicite)
- **Date** : 2026-07-17
- **Décideurs** : Architecte OKI
- **Références** : PlaySocketJS (MIT, rooms, CRDT, server-authoritative, battle-tested >1,3 M duels sur 1 instance Node) ; Geoguess/Geoguess (modÚle de room sans compte, max 5 joueurs) ; prompt v2 §A5 (pas de comptes, pas de leaderboard serveur).
## Contexte
Le prompt v2 interdit comptes et leaderboard serveur. L'usage rĂ©el visĂ© (OKI, AKILPA, salles de classe, Ă©vĂ©nements) crĂ©e pourtant un besoin : **jouer les mĂȘmes 5 lieux que d'autres et comparer les scores**. Deux horizons : maintenant (zĂ©ro serveur) et plus tard (temps rĂ©el, si le projet prouve sa traction).
## Décision
**v1 — DĂ©fi asynchrone par lien Ă  seed partagĂ©e.**
- L'URL de dĂ©fi contient une seed opaque (ex. `/defi/<seed>`) ; le serveur (ou la fonction SvelteKit) dĂ©rive de la seed les mĂȘmes 5 lieux pour tous les joueurs (PRNG seedĂ© cĂŽtĂ© serveur, jamais cĂŽtĂ© client).
- Aucune coordonnée dans l'URL ni dans le payload initial (anti-triche §6 du prompt v2 maintenu).
- Fin de partie : l'image de partage canvas (déjà prévue prompt v2 §4.4) affiche le score ; la comparaison se fait humainement (screenshot dans le groupe WhatsApp/Signal). Zéro serveur d'état, zéro compte, cohérent doctrine.
**v2 — Duel temps rĂ©el via PlaySocketJS, UNIQUEMENT si le gate §"Conditions" est franchi.**
- ModĂšle : rooms sans compte Ă  la Geoguess (nom de room partagĂ©, 2–5 joueurs), Ă©tat synchronisĂ© par PlaySocketJS en mode **server-authoritative** (le serveur valide les guesses et calcule les scores — jamais le client).
- HĂ©bergement : 1 instance Node sur le VPS OKI existant, dans l'enveloppe 40–50 €/mois. Aucune dĂ©pendance Google/Firebase (contraire Ă  Geoguess — voir annexe).
## Options considérées
| Option | Souveraineté | Coût serveur | Effort | Anti-triche | Verdict |
|---|---|---|---|---|---|
| Seed-link asynchrone (v1) | Totale | Nul | S | Serveur dérive les lieux, scores non comparés en ligne | **Retenu v1** |
| PlaySocketJS rooms (v2) | Bonne (MIT, self-host) | 1 instance Node | M | Server-authoritative natif | **Retenu v2 si gate** |
| Socket.io maison | Bonne | 1 instance Node | M–L | À rĂ©implĂ©menter | RejetĂ© (rĂ©invention) |
| Supabase/Firebase Realtime | Mauvaise (cloud propriétaire) | Abonnement | M | Faible cÎté client | Rejeté (doctrine) |
## Conditions de dĂ©clenchement de la v2 (gate — bloquant)
La v2 n'est étudiée que si TOUTES les conditions suivantes sont remplies et documentées dans le rapport de gate :
1. Traction mesurĂ©e : ≄ 30 parties/semaine pendant 4 semaines consĂ©cutives (mesure locale anonymisĂ©e, pas d'analytics tiers).
2. Demande explicite : ≄ 5 retours d'utilisateurs ou partenaires (OKI, AKILPA, enseignants) demandant le dĂ©fi en direct.
3. CapacitĂ© d'hĂ©bergement confirmĂ©e sur le VPS OKI sans dĂ©passer l'enveloppe 40–50 €/mois.
## Vérifications (obligatoires, preuves à l'appui)
- **V1 (bloquant, v1)** — DĂ©terminisme : la mĂȘme URL de dĂ©fi produit les mĂȘmes 5 lieux, dans le mĂȘme ordre, sur deux machines diffĂ©rentes. Preuve : double exĂ©cution filmĂ©e ou logs serveur.
- **V2 (bloquant, v1)** — Anti-fuite : l'URL et les payloads initiaux ne contiennent ni coordonnĂ©es, ni commune, ni `wikidata_id` dĂ©codable. Preuve : inspection rĂ©seau + revue de l'URL.
- **V3 (bloquant, v1)** — L'image de partage n'affiche que score/pseudo libre, jamais les rĂ©ponses des autres joueurs.
- **V4 (bloquant, v2)** — POC PlaySocketJS : room Ă  3 clients, dĂ©connexion/reconnexion d'un joueur sans perte d'Ă©tat (CRDT), score validĂ© cĂŽtĂ© serveur (tentative de triche client rejetĂ©e — preuve par requĂȘte forgĂ©e).
- **V5 (v2)** — Licence : `npm view playsocketjs license` = MIT, consignĂ© dans le rapport.
- **V6 (v2)** — Charge : 10 rooms simultanĂ©es × 5 joueurs sur le VPS sans saturation (preuve : log ressources).
## Rapport (format obligatoire)
```
| ID | Vérification | Méthode / Preuve | PASS/FAIL | Correctif appliqué |
```
## Interdits
- Aucun compte utilisateur, aucun leaderboard global, aucun stockage de pseudos au-delĂ  de la room.
- Aucune dépendance Google/Firebase/Supabase pour le temps réel.
- Aucune copie de code Geoguess/Geoguess (Vue.js) : référence de design uniquement.
- La v2 ne doit pas régresser le hors-scope du prompt v2 pour le solo.
---
# ADR-002 — Carte SVG/topojson lĂ©gĂšre pour les Ă©crans hors-jeu (perf 4G)
- **Statut** : Accepté
- **Date** : 2026-07-17
- **RĂ©fĂ©rences** : composant carte SVG/topojson d'OpenGuessr Education (inspiration uniquement — licence MIT + Commons Clause = non-commercial, **aucune copie de code**) ; geo.api.gouv.fr (contours de communes GeoJSON, gratuit, sans clĂ© — testĂ© OK) ; prompt v2 §4.1 (lazy-load MapLibre) et §4.5 (perf).
## Contexte
MapLibre GL JS pÚse ~800 kB de JS avant la premiÚre tile. L'accueil, le récapitulatif final et les fiches « Aprann » n'ont pas besoin d'une carte interactive complÚte. Sur 4G caribéen, charger MapLibre sur ces écrans détruit le budget < 3 s du prompt v2.
## Décision
- Les écrans **hors-jeu** (accueil 4 régions, mini-carte du récapitulatif, vignette de fiche lieu) utilisent une **carte SVG statique** générée au build depuis un topojson des communes des 4 régions, projection précalculée, aucune tile, aucun JS carte.
- MapLibre reste réservé aux écrans **de jeu** (devinette, résultat, explorer interactif) et reste lazy-loadé.
- Pipeline donnĂ©es : contours communes via `geo.api.gouv.fr/communes?codeRegion=01|02|03|04&fields=nom,code,contour&format=json&geometry=contour` → simplification (mapshaper, seuil Ă  calibrer) → topojson versionnĂ© dans le repo → composant Svelte SVG from scratch (MIT).
## Vérifications
- **V1 (bloquant)** — Budget poids : topojson simplifiĂ© des 4 rĂ©gions ≀ 150 kB gzippĂ© ; preuve : taille du fichier + `gzip -9` consignĂ©s. Si dĂ©passement : simplifier davantage ou dĂ©couper par rĂ©gion avec import dynamique.
- **V2 (bloquant)** — LCP accueil < 2,5 s en throttling 4G (Lighthouse mobile, 3 runs, mĂ©diane consignĂ©e).
- **V3** — Rendu sans JS : la carte SVG s'affiche en SSG avec JS dĂ©sactivĂ© ; chaque rĂ©gion est un `<a>` ou `<button>` rĂ©el (accessibilitĂ© clavier, lecteur d'Ă©cran : nom de rĂ©gion en FR + crĂ©ole).
- **V4** — Attribution donnĂ©es : © contributeurs OpenStreetMap / rĂ©utilisation donnĂ©es publiques mentionnĂ©e dans le footer (licence ODbL pour les contours dĂ©rivĂ©s OSM le cas Ă©chĂ©ant).
- **V5** — ZĂ©ro code copiĂ© d'OpenGuessr Education : revue de provenance, le composant est Ă©crit from scratch (licence MIT du projet).
## Rapport (format obligatoire)
```
| ID | Vérification | Méthode / Preuve | PASS/FAIL | Correctif appliqué |
```
## Interdits
- Ne pas charger MapLibre (ni son CSS) sur l'accueil ou le récapitulatif.
- Ne pas embarquer de tuiles raster de substitution (le SVG est vectoriel pur).
- Ne pas copier le code d'OpenGuessr Education (Commons Clause).
---
# Annexe — Étude Geoguess/Geoguess (rĂ©fĂ©rence, 2026-07-17)
- **Projet** : clone GeoGuessr open source, solo + multijoueur par rooms (nom de room partagĂ©, ≀ 5 joueurs), PWA, cartes custom GeoJSON, licence **MIT**.
- **Stack** : Vue.js + Google Maps StreetView + Firebase → **non rĂ©utilisable** (doctrine zĂ©ro Google ; Vue ≠ Svelte).
- **Signal fort** : leur README admet que la dĂ©mo publique est limitĂ©e par le **prix de l'API Google** et renvoie vers l'auto-dĂ©ploiement avec clĂ© personnelle — justification directe du zĂ©ro propriĂ©taire OKI, Ă  citer dans l'ADR d'architecture initial.
- **À retenir** : (1) le modĂšle de room sans compte (base UX de l'ADR-001 v2) ; (2) le repo **GeoGuess-Maps**, liste de cartes communautaires — modĂšle pour les futurs map-packs JWE contribuĂ©s par la communautĂ© OKI/AKILPA via PR, sans CMS.
-81
View File
@@ -1,81 +0,0 @@
# Mission : JWE — Audit post-exĂ©cution, vĂ©rifications obligatoires & recommandations
## À exĂ©cuter par chaque agent Ă  la fin de sa tĂąche, avant de la marquer « terminĂ©e »
La refonte JWE a été produite à partir du prompt de mission v2 (Svelte 5 + SvelteKit 2, MapLibre, SQLite, 4 régions : Guadeloupe, Martinique, Guyane, La Réunion). Cet audit est la seconde passe. **Aucune tùche n'est terminée sans son rapport de vérification** (format §6). Un FAIL non résolu bloque la livraison. Si le correctif dépasse le scope de l'agent, l'agent produit une RECOMMANDATION argumentée au lieu de coder.
---
## 0. Faits Ă©tablis (Ă  lire avant tout — ils corrigent le prompt v2)
1. **Le QID d'exemple du prompt v2 Ă©tait faux** : Fort DelgrĂšs = `Q3077840` (vĂ©rifiĂ©), pas `Q2216838`. Ses coordonnĂ©es officielles sont 15°59â€Č19″N, 61°43â€Č23″O (≈ 15.9886, -61.7231), pas (16.0036, -61.7319). Si le seed ou l'exemple a Ă©tĂ© copiĂ© tel quel, corriger.
2. **Principe absolu : fetch, pas recall.** Tout QID, toute coordonnĂ©e, toute URL photo, toute licence doit ĂȘtre vĂ©rifiĂ© par une requĂȘte rĂ©elle, jamais depuis la mĂ©moire du modĂšle.
3. **Les communes de Guyane sont immenses** (Maripasoula ≈ 18 000 kmÂČ, Saint-Laurent-du-Maroni ≈ 4 800 kmÂČ). Un radius de score fixe par difficultĂ© y est injuste.
4. **La Réunion est à ~12 000 km des Antilles** : un `maxBounds` global couvrant les 4 régions rend une carte inutilisable (95 % d'océan), et un zoom automatique sur la région du lieu au début du round révÚle la réponse.
5. **Un lieu sans page Wikipedia a rarement une photo sur Wikimedia Commons** : le piÚge « 2-3 lieux sans article frwiki avec photo libre » exige des photos propres (collectif OKI) ou Panoramax.
---
## 1. Agent Contenu — vĂ©rifications donnĂ©es
- **C1 — QID valides (bloquant).** Pour CHAQUE lieu avec `wikidata_id` non null :
```
curl "https://www.wikidata.org/w/api.php?action=wbgetentities&ids=<QID>&props=sitelinks|labels&languages=fr&format=json" -H "User-Agent: JWE-OKI/1.0"
```
Le sitelink `frwiki` doit exister et correspondre au lieu (comparer le label). Preuve : tableau `lieu → QID → sitelink frwiki → OK/KO` dans le rapport.
- **C2 — Les « null » sont de vrais null (bloquant).** Pour chaque lieu Ă  `wikidata_id: null` intentionnel, prouver par une recherche API (`action=query&list=search` sur frwiki) qu'aucun article frwiki n'existe. Sinon le lieu ne teste pas le Cas B.
- **C3 — CoordonnĂ©es prĂ©cises.** Écart < 500 m avec la source officielle (Wikidata `P625`, IGN, geo.api.gouv.fr). Corriger le seed Fort DelgrĂšs (voir §0.1).
- **C4 — Photos sans fuite.** (a) Noms de fichiers opaques (`uuid.webp`, jamais `fort-delgres.webp` — le nom ne doit pas rĂ©vĂ©ler la rĂ©ponse dans les devtools). (b) EXIF strippĂ©s : preuve `exiftool <fichier>` sans GPS ni appareil. (c) Licence vĂ©rifiable : page de description Commons atteignable, crĂ©dit et licence exacts dans le JSON.
- **C5 — Photos des lieux « null ».** Source documentĂ©e : photo propre OKI (crĂ©dit collectif) ou Panoramax (instance + ID + licence). Si introuvable : remplacer le lieu, ne jamais garder une photo sans licence claire.
## 2. Agent Architecte — vĂ©rifications dĂ©cisions
- **A1 — Score juste en Guyane (bloquant).** VĂ©rifier que la formule utilise un radius effectif : `radius_eff = max(radius_difficultĂ©, rayon_commune)` avec `rayon_commune ≈ sqrt(aire_commune / π)`. L'aire peut ĂȘtre prĂ©calculĂ©e dans le JSON du lieu (champ `aire_commune_km2`) ou dĂ©rivĂ©e du contour geo.api.gouv.fr. Test Ă  produire : un guess Ă  40 km du chef-lieu de Maripasoula ne doit pas ĂȘtre quasi-nul.
- **A2 — Pas de fuite de rĂ©gion (bloquant).** Au dĂ©but d'un round, la camĂ©ra ne doit rĂ©vĂ©ler ni la rĂ©gion ni la commune du lieu. Si la solution retenue est la devinette en deux temps (choix de la rĂ©gion via 4 boutons → zoom → placement du marqueur), vĂ©rifier que le scoring combine les deux Ă©tapes et que c'est documentĂ© dans l'ADR. Si une autre solution a Ă©tĂ© codĂ©e, vĂ©rifier l'absence de fuite et l'usage mobile.
- **A3 — ChaĂźne Wikipedia exacte.** `wbgetentities(props=sitelinks)` → titre frwiki → `fr.wikipedia.org/api/rest_v1/page/summary/{titre}`. Cache ≄ 24 h (pas de « temps rĂ©el »). User-Agent explicite cĂŽtĂ© proxy. Fallback de langue documentĂ© : fr → en. **Cas intermĂ©diaire tranchĂ© et documentĂ©** : QID existant mais pas d'article frwiki → recommandĂ© : afficher le Cas B variante « pas encore d'article en français — et si tu le traduisais/crĂ©ais ? ».
- **A4 — DĂ©cision Strapi vs SvelteKit.** L'ADR doit appliquer le critĂšre du prompt (contributeurs non-devs ?). En l'absence de contributeurs non-devs aujourd'hui, la cohĂ©rence doctrine OKI = SvelteKit seul + JSON versionnĂ© + script de validation au commit. Si Strapi a Ă©tĂ© retenu, l'ADR doit justifier par des contributeurs rĂ©els identifiĂ©s.
- **A5 — Hors scope respectĂ©.** Pas de comptes utilisateurs, pas de leaderboard serveur, pas de multijoueur. Scores en `localStorage`. Tout Ă©cart = FAIL ou RECOMMANDATION de suppression.
## 3. Agent Back-end — vĂ©rifications (si applicable)
- **B1 — Endpoint lieu alĂ©atoire sans fuite (bloquant).** Preuve `curl` : la rĂ©ponse ne contient ni `coordonnees`, ni `commune`, ni indice de rĂ©gion ; URL photo opaque.
- **B2 — Score calculĂ© cĂŽtĂ© serveur (bloquant).** Le client envoie `{lieu_id, lat, lon, rĂ©gion_choisie?, indices_utilisĂ©s, temps}` ; le serveur calcule (formule A1) et renvoie score + distance + coordonnĂ©es rĂ©elles. Le client ne calcule rien.
- **B3 — Proxy Wikipedia.** Cache effectif (headers ou store, TTL ≄ 24 h), User-Agent conforme, **aucun texte d'article stockĂ© en base** (contrainte §2.3 du prompt v2).
- **B4 — Pas de dĂ©pendance propriĂ©taire.** Audit `package.json` : zĂ©ro clĂ© API payante, zĂ©ro Google/Mapbox, tĂ©lĂ©mĂ©trie dĂ©sactivĂ©e.
## 4. Agent Front-end — vĂ©rifications
- **F1 — Carte.** Bounds effectifs, aucune fuite de rĂ©gion (voir A2), MapLibre lazy-loadĂ© (vĂ©rifier le chunk rĂ©seau : pas de JS MapLibre sur l'accueil), boutons zoom visibles, navigation clavier.
- **F2 — Cas A / Cas B (bloquant).** Les deux Ă©crans de rĂ©sultat sont **visuellement distincts** (couleur, icĂŽne), le message de lacune est prĂ©sent en FR **et** en crĂ©ole, et le CTA « KontribyĂ© sou WikipĂ©dia » ouvre bien l'Ă©diteur/recherche prĂ©-remplie ou le guide OKI. Captures Ă  l'appui.
- **F3 — Cas intermĂ©diaire gĂ©rĂ©.** QID prĂ©sent mais pas d'article frwiki : pas d'Ă©cran vide ni d'erreur console ; comportement conforme Ă  la dĂ©cision A3.
- **F4 — Écran rĂ©sultat enrichi.** Si le contour de commune est intĂ©grĂ© (geo.api.gouv.fr, gratuit sans clĂ© : `https://geo.api.gouv.fr/communes?code=<insee>&fields=nom,contour&format=json&geometry=contour`), vĂ©rifier l'affichage du polygone et le respect de `prefers-reduced-motion` sur l'animation du marqueur.
- **F5 — Doctrine OKI.** Une seule action primaire par Ă©cran ; bouton Valider en zone du pouce sur mobile ; timer non anxiogĂšne ; micro-interactions en crĂ©ole ; style carte non-Google.
- **F6 — Performance 4G caribĂ©en.** Premier Ă©cran < 3 s en throttling 4G ; photos WebP/AVIF avec `srcset` et lazy loading ; poids d'un round (hors tiles) mesurĂ© et indiquĂ© dans le rapport.
## 5. Agent QA — passe finale
- **Q1 — Rejouer les bloquants.** C1, C2, A1, A2, B1, B2, F2 au minimum, avec preuves.
- **Q2 — Partie complĂšte** dans chaque mode (KonnĂšt moniman, Kote mwen ye ?, DĂ©fi 5 rounds, Aprann), mobile et desktop, dont au moins un round tombant sur un lieu Cas B et un lieu guyanais.
- **Q3 — Checklist OKI du prompt v2 §4 (Agent IntĂ©gration & QA)** intĂ©gralement passĂ©e.
- **Q4 — Scope creep.** Signaler toute feature absente du prompt v2 (auth, comptes, partage rĂ©seau social, analytics
).
- **Q5 — Core Web Vitals** mobile 4G mesurĂ©s (LCP, INP, CLS) et consignĂ©s.
---
## 6. Format du rapport de vérification (obligatoire, un par agent)
```
| ID | VĂ©rification | MĂ©thode / Preuve (commande, requĂȘte, capture) | PASS/FAIL | Correctif appliquĂ© |
|----|--------------|-----------------------------------------------|-----------|--------------------|
```
Suivi d'une section **Recommandations** pour tout ce qui dépasse le scope, triée : `bloquant` / `important` / `confort`, avec pour chacune : problÚme, solution proposée, effort estimé (S/M/L), fichier(s) concerné(s).
Commits atomiques préfixés : `verify:` (preuves), `fix:` (correctifs), `docs:` (ADR, rapport).
## 7. Interdits pendant l'audit
- Aucune nouvelle feature (l'audit corrige et recommande, il n'étend pas).
- Aucun refactor hors correctif de FAIL.
- Aucun PASS sans preuve exécutable ou capture.
- Ne pas « rĂ©parer » une donnĂ©e (QID, coordonnĂ©e, licence) depuis la mĂ©moire : requĂȘte rĂ©elle ou suppression du lieu.