193 lines
8.6 KiB
Markdown
193 lines
8.6 KiB
Markdown
# 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/<campaign_id>/
|
|
autosave_before_dive.json
|
|
autosave_after_dive.json
|
|
autosave_campaign.json
|
|
manual/<save_id>.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.
|