Add mission scoring, rewards collection and save schema v2

Phase "score et récompenses" of the post-0.1.0 plan (user feedback:
wants a more game-like experience with scoring and rewards).

- MissionScoring: 1-3 stars from how each exercise was solved (never
  speed; a finished mission always gives at least one star).
- Save schema v2: mission_results (best/last stars, completions) with a
  v1->v2 migration that keeps existing stars and items.
- Total only grows by the improvement over a mission's best score.
- Data-driven collection (data/rewards, reward_item schema, validated
  incl. mission cross-check) unlocked by RewardRules.
- Reward screen, collection screen, stars on the world map.
- Version 0.2.0.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Denis Defolie
2026-10-11 22:20:59 +02:00
parent 57182172a9
commit 65a0cb46f9
47 changed files with 897 additions and 36 deletions
+18 -1
View File
@@ -45,7 +45,24 @@
"dictation.input_placeholder": "Écris ici...",
"dictation.replay_button": "Réécouter",
"dictation.provisional_audio_badge": "Voix provisoire",
"reward_screen.title": "Mission réussie !",
"reward_screen.title": "Mission terminée !",
"reward_screen.stars_label": "Étoiles gagnées :",
"reward_screen.message_1": "Chaque essai te fait progresser. Tu peux rejouer pour gagner plus d'étoiles !",
"reward_screen.message_2": "Bien joué ! Tu as trouvé toutes les réponses.",
"reward_screen.message_3": "Parfait ! Tu as tout réussi du premier coup.",
"reward_screen.record_label": "Nouveau record pour cette mission !",
"reward_screen.total_label": "Total d'étoiles :",
"reward_screen.new_items_label": "Nouveaux objets dans ta collection :",
"reward_screen.collection_button": "Voir ma collection",
"collection.title": "Ma collection",
"collection.progress_label": "Objets trouvés :",
"collection.locked_title": "Objet mystère",
"collection.unlock_total_stars": "À débloquer avec %d étoiles au total",
"collection.unlock_mission_stars": "À débloquer avec %d étoiles à la mission « %s »",
"collection.back_button": "Retour à la carte",
"collection.owned_marker": "Trouvé",
"world_map.total_stars_label": "Total d'étoiles :",
"world_map.mission_stars_label": "Étoiles :",
"world_map.collection_button": "Ma collection",
"reward_screen.continue_button": "Retour à la carte"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_carte_ancienne",
"title": "Carte ancienne",
"description": "Une vieille carte qui montre des lieux que personne n'a encore visités.",
"unlock": {
"type": "total_stars",
"count": 15
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_cle_argentee",
"title": "Clé argentée",
"description": "Elle ouvre la porte d'un secret caché au fond de la forêt.",
"unlock": {
"type": "total_stars",
"count": 30
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_couronne_feuilles",
"title": "Couronne de feuilles",
"description": "Tressée avec les feuilles de la forêt des accords.",
"unlock": {
"type": "total_stars",
"count": 22
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_cristal_echo",
"title": "Cristal d'écho",
"description": "Il répète en chantant toutes les phrases bien écrites.",
"unlock": {
"type": "total_stars",
"count": 45
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_graine_magique",
"title": "Graine magique",
"description": "Une petite graine qui brille. Elle attend la bonne forêt pour pousser.",
"unlock": {
"type": "total_stars",
"count": 3
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_lanterne_foret",
"title": "Lanterne de la forêt",
"description": "Elle éclaire les chemins quand la nuit tombe sur les mots.",
"unlock": {
"type": "total_stars",
"count": 10
},
"review_status": "draft"
}
+10
View File
@@ -0,0 +1,10 @@
{
"item_id": "item_plume_doree",
"title": "Plume dorée",
"description": "Une plume tombée de l'aile de Plume, le hibou guide.",
"unlock": {
"type": "total_stars",
"count": 6
},
"review_status": "draft"
}
+11
View File
@@ -0,0 +1,11 @@
{
"item_id": "item_trophee_clairiere",
"title": "Trophée de la clairière",
"description": "L'écho de la clairière chante ton nom : tu as tout réussi sans aide.",
"unlock": {
"type": "mission_stars",
"count": 3,
"mission_id": "mission_03_clairiere_echo"
},
"review_status": "draft"
}
+11
View File
@@ -0,0 +1,11 @@
{
"item_id": "item_trophee_pont",
"title": "Trophée du pont",
"description": "Le pont des phrases brille grâce à toi : tu as tout réussi sans aide.",
"unlock": {
"type": "mission_stars",
"count": 3,
"mission_id": "mission_02_pont_des_phrases"
},
"review_status": "draft"
}
+11
View File
@@ -0,0 +1,11 @@
{
"item_id": "item_trophee_pre",
"title": "Trophée du pré",
"description": "Les lutins t'offrent ce trophée : tu as tout réussi sans aide au pré des petits mots.",
"unlock": {
"type": "mission_stars",
"count": 3,
"mission_id": "mission_01_pre_des_mots"
},
"review_status": "draft"
}
+21
View File
@@ -0,0 +1,21 @@
{
"type": "object",
"required": ["item_id", "title", "description", "unlock", "review_status"],
"additionalProperties": false,
"properties": {
"item_id": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" },
"title": { "type": "string", "minLength": 1 },
"description": { "type": "string", "minLength": 1 },
"unlock": {
"type": "object",
"required": ["type", "count"],
"additionalProperties": false,
"properties": {
"type": { "type": "string", "enum": ["total_stars", "mission_stars"] },
"count": { "type": "integer", "minimum": 1 },
"mission_id": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" }
}
},
"review_status": { "type": "string", "enum": ["draft", "validated"] }
}
}
+14
View File
@@ -8,6 +8,7 @@
"session_count",
"unlocked_regions",
"skills",
"mission_results",
"rewards"
],
"additionalProperties": false,
@@ -48,6 +49,19 @@
}
}
},
"mission_results": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": ["best_stars", "last_stars", "completions"],
"additionalProperties": false,
"properties": {
"best_stars": { "type": "integer", "minimum": 1, "maximum": 3 },
"last_stars": { "type": "integer", "minimum": 1, "maximum": 3 },
"completions": { "type": "integer", "minimum": 1 }
}
}
},
"rewards": {
"type": "object",
"required": ["stars", "items"],