Files
Denis Defolie 2fe455f5d1 Add pure correction and pedagogy logic with full test coverage
scripts/correction/: AnswerValidator (multiple choice, sentence
correction via tap-the-faulty-word + retype) and DictationAnalyzer
build the CorrectionResult structure from cahier des charges §6.
Dictation analysis aligns words with WordAligner (Wagner-Fischer edit
distance) before any character comparison, so an omitted or added
word doesn't cascade into false errors on every following word.
TextNormalizer handles non-breaking spaces, typographic apostrophes,
and decomposed-vs-composed French accented characters via an
explicit table (GDScript has no built-in NFC/NFD normalization).
Error classification follows the data annotations only (rule_id
present -> grammatical, absent -> unclassified), never a runtime
linguistic guess.

scripts/pedagogy/: DifficultyManager (per-skill difficulty from
recent results), AttemptFlow (3-attempt graduated aid: hint, then
explanation, then reveal, matching exercise-lifecycle.md's second
gap), and ReviewScheduler (the five mastery states from §9, with
session-counted spaced review intervals [1, 3, 6, 12] rather than
wall-clock dates, since a family device's clock isn't trustworthy).

All of it is pure: no scene tree, no clock or RNG read directly,
every effectful input (session number, attempt outcome) is a
parameter. 65 headless tests across both layers, including the
word-omission-doesn't-cascade regression and the full
decouverte -> acquise -> a_reviser -> acquise path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-11 17:03:14 +02:00

68 lines
1.9 KiB
GDScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class_name TextNormalizer
extends RefCounted
## Normalisation de texte avant comparaison dans les dictees : espaces (dont
## insecables), apostrophes typographiques, et sequences Unicode decomposees
## des caracteres francais ramenees a leur forme composee. GDScript n'expose
## pas de normalisation NFC/NFD native : table explicite ici, comme prevu par
## les "Points durs" du prompt de lancement.
const NBSP := " "
const NARROW_NBSP := " "
const TYPOGRAPHIC_APOSTROPHE := "’"
## Sequences decomposees (lettre de base + marque combinante) -> caractere
## compose. Couvre les caracteres francais courants : accents, cedille, trema.
const DECOMPOSED_TO_COMPOSED := {
"é": "é",
"è": "è",
"ê": "ê",
"ë": "ë",
"É": "É",
"È": "È",
"Ê": "Ê",
"Ë": "Ë",
"à": "à",
"â": "â",
"À": "À",
"Â": "Â",
"ù": "ù",
"û": "û",
"ü": "ü",
"Ù": "Ù",
"Û": "Û",
"Ü": "Ü",
"î": "î",
"ï": "ï",
"Î": "Î",
"Ï": "Ï",
"ô": "ô",
"ö": "ö",
"Ô": "Ô",
"Ö": "Ö",
"ç": "ç",
"Ç": "Ç",
}
static func normalize(text: String) -> String:
var result := text
result = result.replace(NBSP, " ")
result = result.replace(NARROW_NBSP, " ")
result = result.replace(TYPOGRAPHIC_APOSTROPHE, "'")
for decomposed: String in DECOMPOSED_TO_COMPOSED:
result = result.replace(decomposed, DECOMPOSED_TO_COMPOSED[decomposed])
while result.find(" ") != -1:
result = result.replace(" ", " ")
return result.strip_edges()
static func split_words(text: String) -> Array[String]:
var normalized := normalize(text)
var words: Array[String] = []
if normalized.is_empty():
return words
var parts: PackedStringArray = normalized.split(" ", false)
for part in parts:
words.append(part)
return words