From 19b83159a926b4b492300cd14881320d32551b37 Mon Sep 17 00:00:00 2001 From: sucupira Date: Thu, 16 Jul 2026 23:45:17 -0400 Subject: [PATCH] fix(resultat): le marqueur du joueur reste visible, marqueur reel + ligne + etiquette distance + fitBounds (comparaison visuelle facon GeoGuessr) --- app/src/lib/components/GameMap.svelte | 61 ++++++++++++++----------- app/src/routes/defi/[seed]/+page.svelte | 1 + app/src/routes/jeu/[mode]/+page.svelte | 1 + docs/architecture.md | 4 ++ 4 files changed, 40 insertions(+), 27 deletions(-) diff --git a/app/src/lib/components/GameMap.svelte b/app/src/lib/components/GameMap.svelte index 64a8375..403037f 100644 --- a/app/src/lib/components/GameMap.svelte +++ b/app/src/lib/components/GameMap.svelte @@ -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; }; diff --git a/app/src/routes/defi/[seed]/+page.svelte b/app/src/routes/defi/[seed]/+page.svelte index 56f7528..f64796d 100644 --- a/app/src/routes/defi/[seed]/+page.svelte +++ b/app/src/routes/defi/[seed]/+page.svelte @@ -266,6 +266,7 @@ void soumettre(false)} /> diff --git a/app/src/routes/jeu/[mode]/+page.svelte b/app/src/routes/jeu/[mode]/+page.svelte index 3a28724..4436340 100644 --- a/app/src/routes/jeu/[mode]/+page.svelte +++ b/app/src/routes/jeu/[mode]/+page.svelte @@ -260,6 +260,7 @@ void soumettre(false)} /> diff --git a/docs/architecture.md b/docs/architecture.md index 417ab41..b0816ed 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -106,3 +106,7 @@ Règles : - **ADR-001 v1 — Défi par lien à seed** (`/defi/`) : `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, `