# 0007 — Sauvegardes JSON versionnées et remplaçables sans perte - Statut : accepté - Date : 2026-08-10 - Remplace : aucun ## Contexte La campagne contient un état métier durable : argent, dette, position du navire, contrats, archives, épaves connues, cargaison et améliorations. Une plongée contient en plus un état physique très fréquent et temporaire. Tenter de sérialiser les nœuds, les collisions, le maillage sonar ou la position arbitraire du sous-marin couplerait la sauvegarde aux scènes et rendrait les migrations fragiles. Le joueur doit pouvoir conserver plusieurs étapes d'une même campagne, utiliser des sauvegardes manuelles et reprendre après un crash sans perdre un sous-marin à cause d'une écriture interrompue. Les réglages de plateforme et d'accessibilité ne doivent pas être dupliqués dans chaque état de campagne. ## Décision ### Format et enveloppe Chaque sauvegarde est un document JSON explicite. Son enveloppe contient au minimum : ```text schema_version entier positif campaign_id identifiant stable de la partie save_id identifiant stable de cette sauvegarde created_at_utc date ISO 8601 utilisée comme métadonnée d'affichage kind before_dive | after_dive | campaign | manual display_name libellé facultatif d'une sauvegarde manuelle payload état de campagne versionné ``` Les identifiants sont générés et validés par l'application. Un libellé saisi par le joueur n'est jamais utilisé comme nom de fichier ou fragment de chemin. Les horodatages viennent d'une horloge injectée ; ils n'influencent aucune règle de gameplay. Le codec JSON appartient à l'infrastructure. Il convertit entre les dictionnaires du format et des objets de transfert typés, puis entre ces objets et le domaine. Aucun dictionnaire anonyme, chemin de scène, `Node`, `Resource` arbitraire ou objet exécutable ne traverse cette frontière. Le payload contient au minimum : - difficulté, argent et dette ; - `VesselLocation`, disponibilité du sous-marin et ressources non remplies ; - contrats, objectifs connus et révélations ; - zones d'écho, épaves localisées, identités et observations sécurisées ; - états persistants d'ouverture et d'interaction ; - panier, cale, stockage et objets narratifs ; - archives, plans et améliorations ; - dernier résultat de plongée. Les références de contenu utilisent des identifiants stables validés au chargement. Une valeur dérivée est recalculée à partir de ses sources lorsqu'il est possible de le faire sans perte d'information. ### Organisation des fichiers Les campagnes sont rangées sous : ```text user://saves// autosave_before_dive.json autosave_after_dive.json autosave_campaign.json manual/.json active_dive.marker.json ``` Les fichiers temporaires et de secours utilisent des suffixes techniques dans le même dossier que leur cible. Ils ne sont pas affichés comme des sauvegardes. Le `campaign_id` identifie la lignée créée par une nouvelle partie ; il ne représente pas une seconde campagne narrative. Les réglages du joueur sont versionnés et stockés séparément sous `user://settings.json`. Ils comprennent notamment commandes, accessibilité, audio, affichage et qualité Forward+. Charger une autre sauvegarde de campagne ne les remplace pas. ### Politique des emplacements Trois emplacements automatiques ont une signification fixe : - `before_dive` est écrit après validation de la préparation et avant le chargement de la scène sous-marine ; - `after_dive` est écrit après application unique du résultat de plongée au navire et à la campagne ; - `campaign` est écrit après une action durable de progression, notamment transfert, analyse, remise, vente, entretien ou achat. Chaque autosauvegarde conserve sa dernière version valide comme secours invisible. Une sauvegarde manuelle remplacée conserve également sa précédente version valide comme secours technique. Le joueur peut créer jusqu'à dix sauvegardes manuelles nommées, uniquement lorsqu'aucune plongée n'est active. Atteindre la limite exige de choisir et de confirmer un remplacement ou une suppression ; aucune sauvegarde visible n'est effacée automatiquement. `Continuer` charge la sauvegarde principale valide la plus récente parmi tous les emplacements visibles. Si elle est invalide, son secours est essayé, puis la recherche continue avec les autres sauvegardes valides. `Charger` affiche les sauvegardes visibles avec leur statut sans supprimer ni réécrire les fichiers invalides. ### Écriture récupérable Une écriture suit toujours cette procédure : 1. sérialiser vers un fichier temporaire créé dans le dossier de la cible ; 2. fermer le fichier, puis le relire intégralement ; 3. décoder, migrer si nécessaire et valider le document relu ; 4. préserver la cible valide actuelle comme secours ; 5. remplacer la cible par renommage sur le même volume ; 6. restaurer le secours si le remplacement final échoue ; 7. signaler explicitement le succès ou l'erreur à l'application. La dernière sauvegarde valide n'est jamais supprimée avant qu'une nouvelle copie complète ait été écrite et validée. Un échec d'I/O ne devient ni une réussite silencieuse, ni un nouvel état de campagne. Au démarrage, l'infrastructure peut supprimer uniquement les temporaires orphelins dont la cible ou le secours valide a été identifié. Elle ne supprime jamais automatiquement une sauvegarde visible invalide. ### Plongée interrompue Après réussite de l'autosauvegarde `before_dive`, le démarrage effectif de la plongée crée `active_dive.marker.json`. Ce marqueur contient seulement le `campaign_id`, le `save_id` de reprise et un identifiant de session. Il ne contient aucune position, ressource, collision ou donnée sonar. Une fin de plongée traitée avec succès supprime le marqueur après écriture de l'état `after_dive`. Si le jeu redémarre avec un marqueur valide, il propose ou effectue la reprise depuis l'autosauvegarde `before_dive` référencée. Le crash n'inflige ni perte automatique du véhicule, ni consommation intermédiaire. La position arbitraire du sous-marin, les vitesses, particules, collisions, échantillons sonar, couleurs et maillages dynamiques ne sont jamais sauvegardés. ### Versions et migrations `schema_version` existe dès la V1. Le chargement applique des migrations explicites, successives et déterministes d'une version prise en charge vers la version courante, puis valide l'ensemble des invariants et références de contenu. Une fixture anonymisée de chaque version prise en charge est conservée sous `tests/fixtures/`. Les tests vérifient migration, aller-retour, sauvegarde tronquée, référence absente, secours et refus d'une version inconnue. Une version plus récente que celle du jeu est refusée proprement. Le fichier original n'est ni modifié, ni rétrogradé. Une migration réussie en mémoire ne réécrit pas non plus l'original avant une nouvelle sauvegarde explicitement déclenchée. ## Conséquences - Les sauvegardes survivent aux changements de scènes et peuvent être testées sans lancer une plongée. - Plusieurs étapes d'une partie coexistent sans transformer les libellés du joueur en chemins dangereux. - Une plongée ne peut pas être reprise à la seconde exacte de l'interruption ; elle recommence depuis sa préparation validée. - Les sauvegardes et leurs secours consomment davantage d'espace, mais elles ne contiennent pas les données physiques ou sonar volumineuses. - Chaque modification du format exige une migration et des fixtures de contrat. Toute exception nécessite un nouvel ADR. - Les réglages restent disponibles même lorsqu'aucune campagne valide n'existe. - Les erreurs de disque ou de validation restent récupérables et visibles pour le joueur. ## Alternatives considérées - **Sérialiser l'arbre de scènes Godot** : rejeté, car les chemins, nœuds et états physiques ne constituent pas un contrat de sauvegarde stable. - **Sauvegarder pendant la plongée** : rejeté pour la V1, car restaurer une simulation cohérente coûterait beaucoup plus que reprendre avant le départ. - **Une seule sauvegarde écrasée en place** : rejeté, car une interruption d'écriture pourrait détruire toute la progression. - **Un format binaire opaque** : rejeté pour la V1, car il compliquerait inspection, fixtures et migrations sans besoin de taille démontré. - **Inclure les réglages dans chaque sauvegarde** : rejeté, car ils appartiennent au joueur et à la plateforme, pas à une étape de campagne. - **Supprimer automatiquement les sauvegardes invalides ou excédentaires** : rejeté, car la récupération ou le choix doit rester sous contrôle du joueur.