Fix overlapping mission markers on the world map

Manual test: "La clairière" and "La cabane" overlapped on screen.
Their fractional positions (0.8,0.65) and (0.92,0.78) were too close
given the marker size at the project's default window size — exactly
the risk flagged but unverified in the previous commit.

Repositioned all 9 missions (not just the new ones) onto a regular
3x3 grid with 0.35 spacing, well clear of marker dimensions at the
default resolution. Added a headless regression test
(tests/data/test_mission_positions.gd) that fails if any two
missions are closer than a 0.3 fractional gap on both axes — a
data-level guard, not a substitute for visually checking actual
on-screen layout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 21:50:39 +02:00
parent 1c036658bc
commit 233cc26452
13 changed files with 49 additions and 10 deletions
+1 -1
View File
@@ -4,6 +4,6 @@
"narrative_context": "Les lutins du pré n'arrivent plus à accorder leurs mots entre eux. Aide-les à retrouver les bonnes terminaisons pour que leurs phrases retrouvent leur sens !", "narrative_context": "Les lutins du pré n'arrivent plus à accorder leurs mots entre eux. Aide-les à retrouver les bonnes terminaisons pour que leurs phrases retrouvent leur sens !",
"rule_id": "regle_accord_adjectif", "rule_id": "regle_accord_adjectif",
"exercise_type": "multiple_choice", "exercise_type": "multiple_choice",
"map_position": { "x": 0.2, "y": 0.7 }, "map_position": { "x": 0.15, "y": 0.85 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Un pont de pierres portant chacune un mot s'est abîmé. Repère la pierre fautive et corrige-la pour que les voyageurs puissent traverser en toute sécurité.", "narrative_context": "Un pont de pierres portant chacune un mot s'est abîmé. Repère la pierre fautive et corrige-la pour que les voyageurs puissent traverser en toute sécurité.",
"rule_id": "regle_accord_adjectif", "rule_id": "regle_accord_adjectif",
"exercise_type": "sentence_correction", "exercise_type": "sentence_correction",
"map_position": { "x": 0.5, "y": 0.4 }, "map_position": { "x": 0.5, "y": 0.5 },
"review_status": "draft" "review_status": "draft"
} }
+1 -1
View File
@@ -4,6 +4,6 @@
"narrative_context": "L'écho de la clairière répète fidèlement les phrases qu'on lui confie, mais seulement si elles sont bien écrites. Dicte-lui la phrase sans faute pour l'entendre résonner.", "narrative_context": "L'écho de la clairière répète fidèlement les phrases qu'on lui confie, mais seulement si elles sont bien écrites. Dicte-lui la phrase sans faute pour l'entendre résonner.",
"rule_id": "regle_accord_adjectif", "rule_id": "regle_accord_adjectif",
"exercise_type": "dictation", "exercise_type": "dictation",
"map_position": { "x": 0.8, "y": 0.65 }, "map_position": { "x": 0.85, "y": 0.85 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Dans la ruche, chaque mot veut savoir s'il doit rester seul ou se multiplier. Aide les abeilles a choisir la bonne forme avant que le miel ne deborde !", "narrative_context": "Dans la ruche, chaque mot veut savoir s'il doit rester seul ou se multiplier. Aide les abeilles a choisir la bonne forme avant que le miel ne deborde !",
"rule_id": "regle_pluriel_nom", "rule_id": "regle_pluriel_nom",
"exercise_type": "multiple_choice", "exercise_type": "multiple_choice",
"map_position": { "x": 0.12, "y": 0.15 }, "map_position": { "x": 0.15, "y": 0.15 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Les paniers du verger portent des etiquettes, mais certaines se sont trompees de nombre. Trouve l'etiquette fautive et corrige-la avant la recolte.", "narrative_context": "Les paniers du verger portent des etiquettes, mais certaines se sont trompees de nombre. Trouve l'etiquette fautive et corrige-la avant la recolte.",
"rule_id": "regle_pluriel_nom", "rule_id": "regle_pluriel_nom",
"exercise_type": "sentence_correction", "exercise_type": "sentence_correction",
"map_position": { "x": 0.38, "y": 0.12 }, "map_position": { "x": 0.5, "y": 0.15 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Les grenouilles de la mare chantent une phrase, une seule fois. Ecoute-les bien et ecris exactement ce qu'elles chantent.", "narrative_context": "Les grenouilles de la mare chantent une phrase, une seule fois. Ecoute-les bien et ecris exactement ce qu'elles chantent.",
"rule_id": "regle_pluriel_nom", "rule_id": "regle_pluriel_nom",
"exercise_type": "dictation", "exercise_type": "dictation",
"map_position": { "x": 0.65, "y": 0.15 }, "map_position": { "x": 0.85, "y": 0.15 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Chaque mot du sentier cherche son compagnon : le bon petit mot qui l'accompagne toujours. Aide-les a se retrouver sans se tromper.", "narrative_context": "Chaque mot du sentier cherche son compagnon : le bon petit mot qui l'accompagne toujours. Aide-les a se retrouver sans se tromper.",
"rule_id": "regle_accord_determinant", "rule_id": "regle_accord_determinant",
"exercise_type": "multiple_choice", "exercise_type": "multiple_choice",
"map_position": { "x": 0.9, "y": 0.3 }, "map_position": { "x": 0.15, "y": 0.5 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Dans la cabane, les etiquettes des objets ont ete melangees par le vent. Repere celle qui ne va pas et remets le bon petit mot devant.", "narrative_context": "Dans la cabane, les etiquettes des objets ont ete melangees par le vent. Repere celle qui ne va pas et remets le bon petit mot devant.",
"rule_id": "regle_accord_determinant", "rule_id": "regle_accord_determinant",
"exercise_type": "sentence_correction", "exercise_type": "sentence_correction",
"map_position": { "x": 0.92, "y": 0.78 }, "map_position": { "x": 0.85, "y": 0.5 },
"review_status": "draft" "review_status": "draft"
} }
@@ -4,6 +4,6 @@
"narrative_context": "Le vieux rocher de la foret repete une phrase, une seule fois, avant de se rendormir. Ecoute bien et ecris-la sans faute.", "narrative_context": "Le vieux rocher de la foret repete une phrase, une seule fois, avant de se rendormir. Ecoute bien et ecris-la sans faute.",
"rule_id": "regle_accord_determinant", "rule_id": "regle_accord_determinant",
"exercise_type": "dictation", "exercise_type": "dictation",
"map_position": { "x": 0.45, "y": 0.88 }, "map_position": { "x": 0.5, "y": 0.85 },
"review_status": "draft" "review_status": "draft"
} }
+10
View File
@@ -234,3 +234,13 @@ Ajouts à l'arborescence proposée :
- Audio des 6 nouvelles dictées généré avec `make generate-dictation-audio` (voix Piper, point 20), crédité dans `assets/CREDITS.md`. - Audio des 6 nouvelles dictées généré avec `make generate-dictation-audio` (voix Piper, point 20), crédité dans `assets/CREDITS.md`.
**Non vérifié** : la disposition des 9 lieux sur la carte (positions fractionnelles choisies à l'écart les unes des autres, mais jamais vues à l'écran) — à vérifier à la main, voir `docs/testing/manual-checklist.md`. **Non vérifié** : la disposition des 9 lieux sur la carte (positions fractionnelles choisies à l'écart les unes des autres, mais jamais vues à l'écran) — à vérifier à la main, voir `docs/testing/manual-checklist.md`.
## 26. Bug signalé manuellement : deux marqueurs de mission se chevauchaient
**Symptôme** : « La clairière » (`mission_03_clairiere_echo`) et « La cabane » (`mission_08_cabane_etiquettes`) se chevauchaient sur la carte — exactement le risque signalé sans être vérifié au point 25.
**Cause** : leurs positions fractionnelles (0.8, 0.65) et (0.92, 0.78) étaient trop proches pour la taille des marqueurs (`MARKER_HALF_WIDTH`/`MARKER_HALF_HEIGHT` dans `world_map.gd`) à la résolution par défaut du projet (aucune taille de fenêtre fixée dans `project.godot`, donc le viewport par defaut de Godot, 1152x648).
**Correction** : les 9 missions sont replacées sur une grille régulière 3x3 (x, y ∈ {0.15, 0.5, 0.85}), avec un écart de 0.35 entre positions adjacentes — largement supérieur à la largeur/hauteur des marqueurs rapportée à la fenêtre par défaut. Les trois missions historiques (`mission_01` à `_03`) sont déplacées elles aussi, pas seulement les nouvelles, pour que la grille soit cohérente partout plutôt que patchée au cas par cas.
**Garde-fou** : nouveau test headless (`tests/data/test_mission_positions.gd`) qui échoue si deux missions ont un écart fractionnel inférieur à 0.3 sur les deux axes — ne remplace pas une vérification visuelle (l'écart réel en pixels dépend de la taille de fenêtre réelle, non fixée), mais attrape la classe d'erreur qui vient de se produire avant qu'elle ne soit signalée à la main.
+1 -1
View File
@@ -7,7 +7,7 @@ Tout ce qui suit ne peut pas être vérifié sans écran (rendu, mise en page, a
1. `make run`. 1. `make run`.
2. Écran de sélection de profil vide → bouton « Nouveau profil » visible et cliquable. 2. Écran de sélection de profil vide → bouton « Nouveau profil » visible et cliquable.
3. Création de profil : le bouton de validation refuse un prénom vide avec un message visible (pas seulement une absence de réaction) ; CE1 est sélectionné par défaut ; le bouton CE2 bascule correctement l'état visuel des deux boutons (ils doivent se comporter comme des boutons radio, un seul actif à la fois). 3. Création de profil : le bouton de validation refuse un prénom vide avec un message visible (pas seulement une absence de réaction) ; CE1 est sélectionné par défaut ; le bouton CE2 bascule correctement l'état visuel des deux boutons (ils doivent se comporter comme des boutons radio, un seul actif à la fois).
4. Après création, arrivée sur la carte (« La forêt des accords ») avec **neuf** lieux visibles (point 25 de `decisions.md` : extension au-delà du prototype §15) et positionnés à des endroits différents, **jamais vérifié à l'écran avec ce nombre de lieux** — vérifier particulièrement qu'aucun marqueur ne se superpose à un autre, surtout sur petit écran. 4. Après création, arrivée sur la carte (« La forêt des accords ») avec **neuf** lieux visibles (point 25 de `decisions.md` : extension au-delà du prototype §15), disposés sur une grille 3x3 (point 26 : deux missions se chevauchaient avant ce repositionnement) — vérifier qu'aucun marqueur ne se superpose à un autre, y compris en redimensionnant la fenêtre vers une taille réduite.
5. Chaque lieu affiche un intitulé et un état (« À découvrir », etc.) lisible. 5. Chaque lieu affiche un intitulé et un état (« À découvrir », etc.) lisible.
6. Clic sur un lieu → introduction de la mission : le texte narratif s'affiche en entier (pas tronqué), Plume (le hibou) affiche la même phrase dans sa bulle. 6. Clic sur un lieu → introduction de la mission : le texte narratif s'affiche en entier (pas tronqué), Plume (le hibou) affiche la même phrase dans sa bulle.
7. « Commencer la mission » → panneau de règle : énoncé et exemple lisibles, bouton « C'est parti ! » visible. 7. « Commencer la mission » → panneau de règle : énoncé et exemple lisibles, bouton « C'est parti ! » visible.
+28
View File
@@ -0,0 +1,28 @@
extends GutTest
## Garde-fou contre le chevauchement de marqueurs sur la carte (signale
## manuellement : "La clairiere" et "La cabane" se chevauchaient, voir
## docs/specs/decisions.md). scenes/world/world_map.gd place chaque marqueur
## a une position fractionnelle (map_position.x/y de data/missions/*.json) ;
## sans ecart suffisant entre deux missions, leurs boutons se recouvrent a
## l'ecran. Verifie l'ecart plutot que le rendu reel (non testable sans
## ecran), avec une marge volontairement un peu permissive par rapport a la
## grille 0.35 utilisee dans les donnees.
const MIN_FRACTIONAL_GAP := 0.3
func test_mission_markers_do_not_overlap() -> void:
var missions := ExerciseEngine.get_missions()
for i in missions.size():
for j in range(i + 1, missions.size()):
var a: Dictionary = missions[i]["map_position"]
var b: Dictionary = missions[j]["map_position"]
var dx: float = abs(float(a["x"]) - float(b["x"]))
var dy: float = abs(float(a["y"]) - float(b["y"]))
assert_true(
dx >= MIN_FRACTIONAL_GAP or dy >= MIN_FRACTIONAL_GAP,
(
"%s et %s sont trop proches (dx=%s, dy=%s)"
% [missions[i]["mission_id"], missions[j]["mission_id"], dx, dy]
),
)
+1
View File
@@ -0,0 +1 @@
uid://gyps56f0k7tv