Tolerate missing terminal punctuation in dictée answers

Manual test: the Piper voice (previous commit) is now intelligible,
but typing exactly what's heard ("Les petits chats dorment", no
period) was rejected. The provisional audio never vocalizes
punctuation, so nothing tells the child to add it — penalizing its
absence punishes information they were never given.

DictationAnalyzer now strips terminal punctuation (. ! ? …) from both
sides before comparing, in the exact-match/variant check and before
word alignment, without touching punctuation elsewhere in the
sentence. A real error on the last word is still caught (added test).

A scene-level test reproducing the exact reported interaction
(tests/exercises/test_exercise_host_dictation.gd) failed before the
fix and passes after.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 19:17:40 +02:00
parent b0dcedb402
commit 02755ced6f
6 changed files with 123 additions and 6 deletions
+26 -4
View File
@@ -8,6 +8,13 @@ extends RefCounted
## ni hasard. L'explication complete (texte de la regle) est composee par la
## couche d'affichage a partir des rule_id retournes, pas ici.
## Ponctuation de fin de phrase ignoree a la comparaison : l'audio provisoire
## (point 19/20 de docs/specs/decisions.md) ne la fait pas entendre, donc rien
## dans ce que l'enfant ecoute ne lui indique qu'il faut l'ecrire. L'exiger
## serait sanctionner une information qu'il n'a pas recue (CLAUDE.md : une
## erreur declenche une explication, jamais une punition).
const TERMINAL_PUNCTUATION := ".!?…"
static func analyze(dictation: Dictionary, submitted_text: String) -> CorrectionResult:
var normalized_submitted := TextNormalizer.normalize(submitted_text)
@@ -16,8 +23,10 @@ static func analyze(dictation: Dictionary, submitted_text: String) -> Correction
if _matches_target_or_variant(dictation, normalized_submitted, normalized_target):
return CorrectionResult.correct(_build_explanation(0))
var target_words := TextNormalizer.split_words(normalized_target)
var submitted_words := TextNormalizer.split_words(normalized_submitted)
var target_words := TextNormalizer.split_words(_strip_terminal_punctuation(normalized_target))
var submitted_words := TextNormalizer.split_words(
_strip_terminal_punctuation(normalized_submitted)
)
var words_meta: Array = dictation["words"]
# L'alignement se fait insensible a la casse : une majuscule manquante est
@@ -74,14 +83,27 @@ static func analyze(dictation: Dictionary, submitted_text: String) -> Correction
static func _matches_target_or_variant(
dictation: Dictionary, normalized_submitted: String, normalized_target: String
) -> bool:
if normalized_submitted == normalized_target:
var stripped_submitted := _strip_terminal_punctuation(normalized_submitted)
if stripped_submitted == _strip_terminal_punctuation(normalized_target):
return true
for variant: String in dictation.get("accepted_variants", []):
if normalized_submitted == TextNormalizer.normalize(variant):
if stripped_submitted == _strip_terminal_punctuation(TextNormalizer.normalize(variant)):
return true
return false
## Retire les signes de ponctuation terminale (un ou plusieurs, ex. "?!") en
## fin de texte. N'affecte pas la ponctuation interne a la phrase.
static func _strip_terminal_punctuation(text: String) -> String:
var result := text
while (
not result.is_empty()
and TERMINAL_PUNCTUATION.find(result.substr(result.length() - 1)) != -1
):
result = result.substr(0, result.length() - 1)
return result.strip_edges()
static func _classify_error(words_meta: Array, target_index: int, found: String) -> Dictionary:
var meta: Dictionary = words_meta[target_index] if target_index < words_meta.size() else {}
var expected: String = meta.get("text", "")