Fix crash when advancing exercises with a save reloaded from disk (0.5.4)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-12 10:34:40 +02:00
parent 9978a294db
commit f9600cfcf0
4 changed files with 39 additions and 2 deletions
+8
View File
@@ -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é. **É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). **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).
+1 -1
View File
@@ -11,7 +11,7 @@ config_version=5
[application] [application]
config/name="Les Aventuriers des mots" config/name="Les Aventuriers des mots"
config/version="0.5.3" config/version="0.5.4"
run/main_scene="res://scenes/boot/boot.tscn" run/main_scene="res://scenes/boot/boot.tscn"
config/features=PackedStringArray("4.6", "Forward Plus") config/features=PackedStringArray("4.6", "Forward Plus")
config/icon="res://icon.svg" config/icon="res://icon.svg"
+5 -1
View File
@@ -47,7 +47,11 @@ static func next_difficulty_for_skill(
var skills: Dictionary = save_data["skills"] var skills: Dictionary = save_data["skills"]
if not skills.has(skill_id): if not skills.has(skill_id):
return current_difficulty 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) return DifficultyManager.next_difficulty(current_difficulty, recent)
+25
View File
@@ -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 data := ProgressUpdates.record_mission_result(_base_save(), "mission_a", 2)
var errors := SchemaValidator.validate(data, DataLoader.load_schema("save")) var errors := SchemaValidator.validate(data, DataLoader.load_schema("save"))
assert_eq(errors, []) 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")