From f9600cfcf0b6ccfb0f636e99f1edfb7d19c0db77 Mon Sep 17 00:00:00 2001 From: Denis Defolie Date: Mon, 12 Oct 2026 10:34:40 +0200 Subject: [PATCH] Fix crash when advancing exercises with a save reloaded from disk (0.5.4) Co-Authored-By: Claude Sonnet 5.5 --- docs/specs/decisions.md | 8 ++++++++ project.godot | 2 +- scripts/profiles/progress_updates.gd | 6 +++++- tests/profiles/test_progress_updates.gd | 25 +++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/specs/decisions.md b/docs/specs/decisions.md index fc16de8..80d5010 100644 --- a/docs/specs/decisions.md +++ b/docs/specs/decisions.md @@ -338,3 +338,11 @@ Ajouts à l'arborescence proposée : **Élisions** : aucune élision manquante (« le arbre ») dans les données, vérifié sur tous les JSON y compris les listes de jetons ; garde-fou permanent `tests/data/test_elision.gd` (mots à « h » aspiré et exceptions listés ; l'assertion arrière écarte « écris-la en »). Si l'élision manquante vue en jeu vient d'ailleurs que des données, il faudra l'indication de l'écran concerné. **Phrases en double** : 14 phrases apparaissaient dans deux exercices ou dans un exercice et une dictée (ex. « Le chat dort. »). Les énoncés des QCM et corrections concernés ont été remplacés par de nouvelles phrases originales (indice et explication ajustés) ; les dictées, dont l'audio est généré, n'ont pas été touchées. Garde-fou permanent `tests/data/test_no_duplicate_sentences.gd` (phrase reconstituée en version correcte, comparée sans casse ni ponctuation). + +## 35. Plantage après redémarrage, profil relu depuis le disque (version 0.5.4) + +**Bug** : `ProgressUpdates.next_difficulty_for_skill` affectait le tableau `recent_results` d'une sauvegarde relue depuis le JSON (un `Array` non typé) à un `Array[bool]` : erreur de script, l'exercice suivant ne se chargeait pas (blocage/plantage), dès qu'un profil avait déjà de l'historique sur la notion. Les tests ne le voyaient pas car ils construisaient l'état en mémoire (tableaux typés) sans passer par le disque. Signalé par l'utilisateur sur « La serre aux étiquettes » (profil CE2 existant). Corrigé par recopie dans un `Array[bool]` ; test de non-régression qui passe l'état par `JSON.stringify`/`parse`. + +**Vérification** : copie des vrais profils de l'utilisateur dans un profil temporaire, puis parcours de chaque écran (récompense, carte, collection, espace adulte, réglages, introduction, choix de profil) et chargement de toutes les missions de leur niveau : aucune erreur de script. À retenir : tout état relu de JSON doit être converti avant d'être affecté à un tableau typé. + +**Son (non reproduit)** : sur la machine de développement, les exports Linux (4.6.3 et 4.7.2) jouent bien la voix de la dictée par le parcours réel (flux PipeWire non coupé, niveau mesuré sur le moniteur du périphérique). Cause de l'absence de son chez l'utilisateur non identifiée ; diagnostic demandé (pilote audio, sortie par défaut, messages console). diff --git a/project.godot b/project.godot index cb5a054..ab2be91 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="Les Aventuriers des mots" -config/version="0.5.3" +config/version="0.5.4" run/main_scene="res://scenes/boot/boot.tscn" config/features=PackedStringArray("4.6", "Forward Plus") config/icon="res://icon.svg" diff --git a/scripts/profiles/progress_updates.gd b/scripts/profiles/progress_updates.gd index abee3b5..113ca02 100644 --- a/scripts/profiles/progress_updates.gd +++ b/scripts/profiles/progress_updates.gd @@ -47,7 +47,11 @@ static func next_difficulty_for_skill( var skills: Dictionary = save_data["skills"] if not skills.has(skill_id): return current_difficulty - var recent: Array[bool] = skills[skill_id]["recent_results"] + # Relu depuis le disque (JSON), le tableau est un Array non type : on le + # recopie dans un Array[bool] plutot que de l'affecter directement. + var recent: Array[bool] = [] + for result: Variant in skills[skill_id]["recent_results"]: + recent.append(bool(result)) return DifficultyManager.next_difficulty(current_difficulty, recent) diff --git a/tests/profiles/test_progress_updates.gd b/tests/profiles/test_progress_updates.gd index 503ead2..ee4f9fe 100644 --- a/tests/profiles/test_progress_updates.gd +++ b/tests/profiles/test_progress_updates.gd @@ -124,3 +124,28 @@ func test_record_mission_result_validates_against_the_save_schema() -> void: var data := ProgressUpdates.record_mission_result(_base_save(), "mission_a", 2) var errors := SchemaValidator.validate(data, DataLoader.load_schema("save")) assert_eq(errors, []) + + +func test_next_difficulty_works_on_a_save_reloaded_from_disk() -> void: + # Regression : relu depuis le JSON, "recent_results" est un Array non type + # (et les entiers deviennent des flottants). L'affecter a un Array[bool] + # faisait echouer le choix de l'exercice suivant apres un redemarrage. + var save := SaveFactory.new_save("test_disk_reload", "Lea", "CE2") + save["skills"] = { + "homophones_grammaticaux": + { + "status": "en_consolidation", + "recent_results": [true, true, true, true, true, true, true, true], + "recent_hint_used": [false, false, false, false, false, false, false, false], + "sessions_succeeded": [1, 3], + "formats_succeeded": ["dictation"], + "last_session_practiced": 3, + "review_interval_index": -1, + "next_review_session": 4, + } + } + var reloaded: Dictionary = JSON.parse_string(JSON.stringify(save)) + var difficulty := ProgressUpdates.next_difficulty_for_skill( + reloaded, "homophones_grammaticaux", 1 + ) + assert_eq(difficulty, 2, "huit reussites de suite : la difficulte monte")