mirror of
https://github.com/deunix-educ/aventuriers-des-mots.git
synced 2026-10-13 02:11:58 +02:00
Add CE2 homophones chapter (a/à, et/est, son/sont, on/ont)
Same directives as the CE1 chapter: 4 rules with a substitution test each, 8 multiple-choice + 4 sentence corrections + 3 dictées per rule (draft), 3 missions per rule, Piper audio for the 12 dictées. 21 missions don't fit on one map, so the world map now shows the missions of the profile's school level (derived from the rule's level, no data-format change), with a button letting a CE2 profile revisit the CE1 map. Mission-position guard test now checks each level separately. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,28 +1,32 @@
|
||||
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.
|
||||
## docs/specs/decisions.md, point 26). 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 affichees
|
||||
## ensemble (meme niveau), leurs boutons se recouvrent a l'ecran. Verifie
|
||||
## l'ecart plutot que le rendu reel (non testable sans ecran), a la
|
||||
## resolution par defaut (1152 px de large : 0.2 = 230 px pour des
|
||||
## marqueurs de 160 px ; 0.3 de hauteur pour des marqueurs de 72 px).
|
||||
|
||||
const MIN_FRACTIONAL_GAP := 0.3
|
||||
const MIN_GAP_X := 0.2
|
||||
const MIN_GAP_Y := 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]
|
||||
),
|
||||
)
|
||||
func test_mission_markers_do_not_overlap_within_a_level() -> void:
|
||||
for level in ["CE1", "CE2"]:
|
||||
var missions := ExerciseEngine.get_missions_for_level(level)
|
||||
assert_false(missions.is_empty(), "aucune mission pour %s" % level)
|
||||
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_GAP_X or dy >= MIN_GAP_Y,
|
||||
(
|
||||
"%s et %s sont trop proches (dx=%s, dy=%s)"
|
||||
% [missions[i]["mission_id"], missions[j]["mission_id"], dx, dy]
|
||||
),
|
||||
)
|
||||
|
||||
@@ -15,11 +15,18 @@ func test_unknown_rule_returns_empty_dictionary() -> void:
|
||||
|
||||
func test_missions_are_loaded_and_sorted() -> void:
|
||||
var missions := ExerciseEngine.get_missions()
|
||||
assert_eq(missions.size(), 9)
|
||||
assert_eq(missions.size(), 21)
|
||||
var ids: Array = missions.map(func(m: Dictionary) -> String: return m["mission_id"])
|
||||
assert_eq(ids, ids.duplicate()) # trivialement vrai, documente l'ordre stable
|
||||
assert_true(ids.has("mission_01_pre_des_mots"))
|
||||
assert_true(ids.has("mission_09_rocher_qui_repete"))
|
||||
assert_true(ids.has("mission_21_colline_echos"))
|
||||
|
||||
|
||||
func test_missions_are_filtered_by_the_level_of_their_rule() -> void:
|
||||
assert_eq(ExerciseEngine.get_missions_for_level("CE1").size(), 9)
|
||||
assert_eq(ExerciseEngine.get_missions_for_level("CE2").size(), 12)
|
||||
assert_eq(ExerciseEngine.get_missions_for_level("CM1").size(), 0)
|
||||
|
||||
|
||||
func test_get_mission_returns_the_requested_mission() -> void:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
extends GutTest
|
||||
## La carte n'affiche que les missions du niveau du profil ; un profil CE2 peut
|
||||
## basculer sur la carte CE1 pour revoir les notions precedentes (point 27 de
|
||||
## docs/specs/decisions.md).
|
||||
|
||||
var _profile_id: String = ""
|
||||
|
||||
|
||||
func after_each() -> void:
|
||||
ProfileManager.delete_profile(_profile_id)
|
||||
|
||||
|
||||
func _open_map_for(level: String) -> Control:
|
||||
_profile_id = ProfileManager.create_profile("Test", level)
|
||||
ProfileManager.select_profile(_profile_id)
|
||||
var map: Control = load("res://scenes/world/world_map.tscn").instantiate()
|
||||
add_child_autofree(map)
|
||||
return map
|
||||
|
||||
|
||||
func test_ce1_profile_sees_only_ce1_missions_and_no_toggle() -> void:
|
||||
var map := _open_map_for("CE1")
|
||||
await wait_process_frames(1)
|
||||
|
||||
assert_eq(map.get_node("%MapArea").get_child_count(), 9)
|
||||
assert_false(map.get_node("%LevelToggleButton").visible)
|
||||
|
||||
|
||||
func test_ce2_profile_sees_ce2_missions_and_can_review_ce1() -> void:
|
||||
var map := _open_map_for("CE2")
|
||||
await wait_process_frames(1)
|
||||
|
||||
assert_eq(map.get_node("%MapArea").get_child_count(), 12)
|
||||
assert_eq(map.get_node("%TitleLabel").text, UIText.get_text("world_map.title_ce2"))
|
||||
var toggle: Button = map.get_node("%LevelToggleButton")
|
||||
assert_true(toggle.visible)
|
||||
|
||||
toggle.pressed.emit()
|
||||
await wait_process_frames(1)
|
||||
assert_eq(map.get_node("%MapArea").get_child_count(), 9)
|
||||
assert_eq(map.get_node("%TitleLabel").text, UIText.get_text("world_map.title"))
|
||||
|
||||
toggle.pressed.emit()
|
||||
await wait_process_frames(1)
|
||||
assert_eq(map.get_node("%MapArea").get_child_count(), 12)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b07gkjubhbj8w
|
||||
Reference in New Issue
Block a user