Implement production vertical slice foundation

This commit is contained in:
chindit committed 2026-10-02 15:27:48 +02:00
1 parent 9d4a89f355
commit a18e82eb44
104 files changed
+10595 -268

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
# 0004 — Forward+ comme renderer unique de production
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
Les POC A et B ont été validés avec GL Compatibility afin de faciliter les
premiers essais. Cette configuration a aussi conduit le POC A à simuler la
diffusion des projecteurs avec des plans translucides dépendants du point de
vue. Ces compromis restent documentés dans les baselines, mais ne répondent pas
à la direction visuelle de la production : matériaux PBR, éclairage sous-marin,
brouillard, particules et ombres réglables.
Maintenir plusieurs renderers augmenterait le nombre de configurations à
tester et encouragerait des chemins graphiques divergents. Les performances
doivent être ajustées par la qualité des effets, pas par un changement de
renderer qui modifierait le rendu et les capacités disponibles.
## Décision
Forward+ est l'unique renderer pris en charge par le code et les builds de
production. Le jeu ne propose ni sélection de renderer, ni repli automatique
vers GL Compatibility.
Les POC demeurent des références historiques jetables dans leur configuration
GL Compatibility documentée. Ils ne définissent pas une seconde configuration
prise en charge et leur code ou leurs artifices graphiques ne sont pas repris
en production.
Les réglages d'affichage permettent d'adapter les performances sans quitter
Forward+. Ils couvrent au minimum des niveaux de qualité pour les particules,
les ombres, la distance d'affichage et le post-traitement. Les effets
atmosphériques coûteux, dont le brouillard volumétrique s'il est retenu après
mesure, suivent la même politique. Des préréglages cohérents peuvent exposer
ces valeurs ensemble, avec des réglages détaillés lorsque cela reste lisible.
Une baisse de qualité ne change aucune règle de gameplay et ne peut supprimer
une information indispensable à la navigation, aux alarmes ou à la lecture du
sonar. Les choix sont persistés dans les réglages du joueur, séparément de la
sauvegarde de campagne.
Chaque mesure de performance indique le renderer Forward+, la résolution, le
préréglage graphique, la machine, le build et la scène testée. La première
scène de production mesure explicitement le coût des projecteurs, des ombres,
du brouillard et des particules avant la phase de finition visuelle.
## Conséquences
- Le rendu de production possède une seule configuration architecturale à
développer et vérifier.
- Les machines ne prenant pas correctement en charge Forward+ ne sont pas des
cibles de la V1.
- Les performances faibles se traitent par des niveaux de qualité mesurés,
des LOD et des budgets d'effets, sans branche de rendu alternative.
- Le mode bas conserve la lisibilité fonctionnelle à 1080p tout en réduisant
particules, ombres, distance d'affichage et post-traitement.
- Le comportement visuel doit être vérifié sur macOS ARM64, Windows et Linux
avant la fin de la verticale.
- Les plans translucides utilisés comme volumes de phare dans le POC A ne sont
pas promus ; les projecteurs de production sont reconstruits et profilés.
## Alternatives considérées
- **GL Compatibility comme renderer de production** : rejeté, car ses limites
contrarient les besoins d'éclairage et d'atmosphère visés.
- **Forward+ avec GL Compatibility comme mode de secours** : rejeté, car cela
imposerait deux chemins graphiques, deux profils de performance et davantage
de régressions visuelles possibles.
- **Mobile comme renderer de secours** : rejeté pour la V1 pour les mêmes
raisons. Une cible matérielle future nécessiterait un nouvel ADR et une
validation graphique dédiée.
@@ -0,0 +1,139 @@
# 0005 — Ticks de plongée fixes et mises à jour typées
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
La plongée combine une physique Godot, des ressources continues, des dégâts
ponctuels, des outils électriques, des alarmes et plusieurs présentations. Si
chaque acteur ou interface utilise sa propre horloge ou recalcule une partie de
ces règles, la pause, les variations de framerate et l'ordre des signaux peuvent
produire des résultats différents.
Les informations n'ont pas toutes la même nature. Une commande exprime une
intention, la télémétrie décrit une mesure du monde, un snapshot décrit l'état
autoritaire à un instant et un événement rapporte un fait survenu pendant une
transition. Les regrouper dans un dictionnaire ou un signal générique rendrait
leur propriétaire et leur temporalité implicites.
## Décision
### Horloge et cadence
La simulation de plongée utilise une cadence de physique configurée à 60 Hz.
L'application fournit exactement un `DiveStep` au domaine pour chaque tick de
physique accepté. Le domaine n'accède ni à l'horloge système, ni au framerate,
ni à l'arbre de scènes.
Un `DiveTick` contient au minimum l'identifiant de session, une séquence
strictement croissante et la durée du pas en secondes. La durée normale vaut
`1 / 60` seconde. Un pas nul, négatif, non fini ou désordonné est refusé. Un pas
supérieur à 0,1 seconde est subdivisé de manière déterministe par l'application
ou refusé ; il n'est jamais appliqué comme un grand saut unique.
La pause suspend la production de `DiveStep`. À la reprise, le temps passé en
pause n'est ni simulé, ni rattrapé. Les tests utilisent des ticks construits
explicitement et n'attendent jamais le temps réel.
### Contrats d'entrée séparés
- `VehicleCommand` contient l'intention de propulsion issue du clavier ou d'un
autre périphérique remappé. La souris n'oriente jamais le sous-marin.
- `CameraLookCommand` contient uniquement le mouvement de regard dans la
cabine. Il ne traverse pas le contrat de pilotage du véhicule.
- `VehicleTelemetry` est un snapshot immuable produit par l'acteur physique :
transform, vitesses, profondeur et efforts réellement appliqués.
- `DiveTelemetrySample` réduit cette télémétrie aux mesures nécessaires aux
règles de plongée. Le domaine ne reçoit aucun `Node` ou `Transform3D` Godot.
- `CollisionObservation` décrit un impact ponctuel avec une séquence stable et
sa vitesse normale en mètres par seconde. Les observations sont traitées de
manière idempotente.
- `ToolPowerDemand`, `EjectionIntent` et `ReturnRouteSample` décrivent
séparément la demande électrique des outils, l'intention d'éjection et les
données connues pour estimer le retour.
- `DiveStep` agrège ces entrées pour un tick sans changer leur responsabilité.
Ces frontières utilisent des objets typés. Les dictionnaires anonymes restent
confinés aux codecs ou aux prototypes et ne traversent pas la production.
### Règles résolues et session
Au début de chaque plongée, `DiveRulesResolver` construit un
`ResolvedDiveRules` immuable en appliquant, dans cet ordre :
1. les règles de base ;
2. les capacités et améliorations du sous-marin ;
3. le profil de difficulté ;
4. les aides d'accessibilité qui modifient le gameplay ;
5. la validation et le bornage des valeurs.
`DiveSession` est l'unique propriétaire de l'oxygène, de la batterie, de la
coque, des seuils de pression, des alertes, de l'estimation de retour et de
l'état terminal de la plongée. C'est un objet de domaine pur : il reçoit un
`DiveStep`, applique les règles résolues dans un ordre fixe et retourne un
`DiveUpdate`.
L'ordre d'un pas est : validation, consommation, pression, collisions ordonnées,
éjection, estimation du retour, alertes, état terminal, puis publication. Une
session terminale ne rejoue pas sa conséquence et refuse toute mutation de
gameplay ultérieure.
### Snapshot et événements
Chaque pas accepté produit un seul `DiveUpdate` cohérent, associé à la session
et à la séquence du tick. Il contient :
- un `DiveSnapshot` immuable représentant l'état autoritaire après le pas ;
- zéro ou plusieurs événements typés décrivant uniquement les faits survenus
pendant ce pas.
Le snapshot contient les valeurs actuelles et maximales, leurs unités et leurs
ratios pour batterie, oxygène, coque, pression et profondeur, ainsi que
l'estimation du retour, les alertes, l'éjection et l'éventuel motif terminal.
Il permet à un nouvel écran de se synchroniser sans rejouer l'historique.
Les événements, tels que `AlertLevelChanged`, `HullDamaged`,
`ResourceDepleted` et `DiveTerminated`, déclenchent les réactions ponctuelles :
audio, animation, journal ou transition applicative. Ils portent une identité
ou une séquence permettant aux adaptateurs d'éviter un double traitement. Un
événement ne remplace jamais une valeur persistante du snapshot.
La présentation et l'audio consomment `DiveUpdate`. Ils ne recalculent ni
seuils, ni dégâts, ni consommation, ni fin de plongée. Ils ne mutent jamais
directement `DiveSession` ou l'acteur physique ; toute action repart sous forme
de commande applicative.
## Conséquences
- Une même suite de règles, commandes, télémétries et observations produit le
même résultat dans les tests et dans le jeu.
- La pause est une absence de ticks, pas un multiplicateur de temps dispersé
entre plusieurs systèmes.
- Le HUD peut afficher des valeurs exactes et des pourcentages sans posséder
une copie concurrente des ressources.
- L'audio peut réagir une seule fois aux transitions tout en consultant l'état
courant à tout moment.
- La conversion entre les types Godot et les données pures nécessite des
adaptateurs explicites.
- Ajouter une nouvelle entrée ou sortie de simulation demande un contrat nommé
et des tests, mais évite les clés implicites et les dépendances de scène.
- Les consommations indépendantes du tick de plongée, notamment les actions de
port, restent des cas d'usage de campagne distincts.
## Alternatives considérées
- **Chaque système utilise `_process(delta)`** : rejeté, car les règles
dépendraient du framerate et leur ordre serait difficile à garantir.
- **`DiveSession` lit directement la physique et l'horloge Godot** : rejeté,
car le domaine ne serait plus pur ni testable sans scène.
- **Un dictionnaire unique de données par frame** : rejeté, car commandes,
mesures, états et événements auraient une temporalité implicite.
- **Publier uniquement des événements** : rejeté, car un écran ouvert plus tard
devrait reconstruire l'état en rejouant l'historique.
- **Publier uniquement un snapshot** : rejeté, car les consommateurs devraient
comparer deux états et réinventer les transitions ponctuelles.
- **Rattraper les ticks après une pause ou un ralentissement important** :
rejeté, car cela provoquerait des consommations et dégâts invisibles ou des
pics de calcul au retour.
@@ -0,0 +1,149 @@
# 0006 — Contrat sonar commun et porte de sélection de l'acquisition
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
Le POC B a validé une représentation bathymétrique continue, révélée
progressivement, qui rend le relief navigable. Son acquisition principale
utilise toutefois des sondes verticales disposées devant le véhicule et peut
montrer le dessus de surfaces qu'un faisceau frontal ne verrait pas. Une passe
frontale distincte détecte les anomalies métalliques. Cette combinaison est
lisible, mais elle n'est ni une simulation acoustique fidèle, ni une
implémentation de production.
Une acquisition entièrement frontale serait plus cohérente avec la position du
capteur, mais risque de produire des trous, de perdre la continuité du relief ou
de rendre le sonar à nouveau difficile à interpréter. Le comportement validé du
POC doit donc rester la référence pendant l'expérience, sans importer son code
jetable dans la production.
L'acquisition, la reconstruction et la présentation doivent pouvoir évoluer
séparément. Sinon, comparer deux méthodes imposerait aussi de réécrire le
renderer et rendrait leurs résultats impossibles à évaluer équitablement.
## Décision
### Contrat commun
Toutes les acquisitions candidates produisent le même `SonarScan`. Celui-ci
contient au minimum :
- un identifiant de scan et l'identifiant de la session ;
- le transform du capteur figé à l'émission ;
- la portée, les ouvertures et la durée de révélation ;
- les dimensions logiques de la grille ;
- un `SonarSampleBuffer` immuable ;
- des `SonarContact` distincts ;
- l'échelle de couleur et les valeurs réelles minimales et maximales ;
- l'état prêt, capture, révélation ou récupération.
Le repère du scan est figé au déclenchement, avec son origine au capteur, +X à
tribord, +Y vers le haut et +Z vers l'avant, en mètres. L'adaptateur du monde
convertit le -Z avant de Godot vers ce contrat. Aucun consommateur ne déduit ou
réinterprète cette convention.
`SonarSampleBuffer` encapsule des tableaux parallèles compacts de positions,
normales, intensités, élévations réelles, matériaux et statuts d'observation.
Un même index décrit un échantillon ; la ligne et la colonne sont dérivées des
dimensions de grille. Un accesseur typé comme `sample_at(row, column)` masque la
représentation interne et vérifie les bornes. Le buffer devient immuable avant
sa publication.
Les contacts ne sont pas obtenus en moyennant tous les retours d'un matériau.
Un clustering spatial déterministe produit plusieurs `SonarContact`, chacun
avec une identité, un matériau, un centre, des limites, une intensité maximale
et un nombre d'échantillons.
### Acquisition, reconstruction et présentation
L'adaptateur sonar du monde possède l'acquisition physique. Le service
applicatif valide et structure les échantillons ainsi que les contacts. La
présentation reconstruit et affiche le relief à partir du scan publié ; elle ne
fait ni classification métier, ni détection de mission.
La palette d'altitude reste relative au scan pour préserver le contraste, mais
les élévations réelles en mètres sont conservées et une légende chiffrée indique
l'échelle employée. Noir représente l'inconnu, pas une profondeur arbitraire.
Un seul scan courant et un seul scan précédent en fondu existent au maximum.
La vue compacte et la vue agrandie affichent la même texture produite par un
renderer unique. Elles ne reconstruisent pas deux maillages indépendants.
La révélation de référence dure 2,5 secondes, puis la récupération 0,35 seconde.
Une nouvelle demande n'est pas mise en file pendant cette séquence. Ces valeurs
restent configurables et testées, mais leur temporalité appartient au service
sonar, pas au HUD.
### Porte de sélection
Deux acquisitions sont réécrites derrière ce contrat uniquement pendant une
calibration bornée :
1. la méthode de référence inspirée du POC B, combinant une grille
bathymétrique lisible et une détection frontale des anomalies ;
2. une méthode par faisceau frontal, reconstruite dans le même buffer.
La comparaison utilise les mêmes poses de capteur, scènes, portées, dimensions
de sortie et cas de test : pente, chenal, rupture de relief, épave, ouverture,
zones masquées et plusieurs contacts métalliques séparés. Elle mesure au
minimum :
- la compréhension du relief et d'un passage par des testeurs ;
- la détection de l'épave et de plusieurs anomalies sans fusion incorrecte ;
- la stabilité face aux trous et aux rotations du véhicule ;
- le coût CPU moyen et maximal, les allocations et le coût de reconstruction ;
- la conservation de l'effet de révélation validé.
Le faisceau frontal n'est retenu que s'il atteint une lisibilité et une
stabilité au moins équivalentes à la réécriture de référence dans les budgets
de la verticale. Dans le cas contraire, la méthode inspirée du POC devient
l'acquisition de production. La fidélité acoustique ne prime pas sur la
capacité du joueur à lire le relief et à naviguer.
La porte produit un compte rendu et un ADR de sélection. Après cette décision,
une seule acquisition reste autoritaire dans `game/`. L'autre implémentation
n'est pas maintenue comme chemin de secours ; les scènes, données de calibration
et résultats comparatifs peuvent être conservés pour prévenir les régressions.
### Persistance
Ni les échantillons bruts, ni le buffer, ni le maillage, ni les couleurs d'un
scan sous-marin ne sont sauvegardés. La campagne persiste seulement les faits
sémantiques sécurisés : zones d'écho de surface, épaves localisées, identités et
observations. Une sauvegarde ne tente jamais de reconstruire une image sonar en
cours de révélation.
## Conséquences
- Le comportement apprécié dans le POC reste disponible comme référence et
issue sûre sans promouvoir son code.
- Une acquisition peut être remplacée sans modifier le renderer, le HUD ou les
règles de mission.
- Le buffer compact limite les allocations, mais demande un accesseur typé et
des tests d'alignement de tous les tableaux.
- Les conversions de coordonnées sont explicites et testables.
- Plusieurs anomalies restent distinctes, contrairement à la moyenne globale
provisoire du POC.
- Le rendu sonar possède une seule reconstruction et une seule texture, ce qui
réduit les divergences entre les deux tailles d'affichage.
- La calibration ajoute un coût court à la tranche sonar, borné par une porte
de décision et supprimant ensuite le chemin perdant.
## Alternatives considérées
- **Copier le sonar du POC B dans la production** : rejeté, car cela
introduirait ses dépendances, sa reconstruction complète et ses contrats
implicites dans `game/`.
- **Choisir immédiatement le faisceau frontal** : rejeté, car sa vraisemblance
ne garantit pas la lisibilité déjà difficile à obtenir.
- **Conserver durablement les deux acquisitions** : rejeté, car deux
implémentations autoritaires doubleraient réglages, tests et risques de
régression.
- **Publier directement des nœuds, maillages ou dictionnaires depuis le monde** :
rejeté, car acquisition et présentation resteraient couplées.
- **Sauvegarder la carte sonar brute** : rejeté, car elle représente une vue
temporaire dérivée du monde et alourdirait le format sans bénéfice de
progression.
+192
View File
@@ -0,0 +1,192 @@
# 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.
@@ -0,0 +1,128 @@
# 0008 — Services possédés et injectés par la composition
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
Les réglages, l'accès aux sauvegardes, l'audio et une campagne chargée doivent
survivre à plusieurs changements d'écran. Godot permet de résoudre ce besoin
avec des autoloads, des singletons statiques ou un bus de signaux global. Ces
solutions rendent toutefois les dépendances accessibles depuis n'importe quel
script, masquent leur durée de vie et compliquent l'isolation des tests.
La verticale possède déjà une couche `composition`, seule autorisée à connaître
les implémentations concrètes de toutes les autres couches. Elle peut donc
maintenir les objets de longue durée sans les transformer en état global.
## Décision
### Aucun accès global de gameplay
Aucun autoload personnalisé, singleton statique, registre global, service
locator ou bus de signaux global n'est introduit pour le gameplay de la
verticale. Une longue durée de vie ne suffit pas à justifier un accès global.
Les singletons fournis par Godot restent des détails de plateforme. Seuls les
adaptateurs autorisés les utilisent ; le domaine et les cas d'usage ne lisent
pas directement `Input`, `SceneTree`, `AudioServer`, les fichiers ou l'horloge.
### Racine persistante
`vertical_slice_root.tscn` est la racine de composition persistante de la
verticale. Elle crée les implémentations concrètes, les relie à leurs ports et
possède un emplacement destiné à l'écran ou au monde actif. Une transition
remplace ce contenu enfant sans remplacer la racine et sans demander aux scènes
de rechercher des services dans l'arbre.
La composition est responsable de :
- construire les adaptateurs d'infrastructure et les cas d'usage ;
- charger ou créer l'état de campagne ;
- injecter les contrats requis avant d'activer une scène ;
- connecter les sorties d'une scène aux commandes applicatives appropriées ;
- déconnecter et libérer les dépendances de portée courte lors d'une
transition ;
- choisir l'écran suivant à partir d'un résultat applicatif explicite.
Une scène ne parcourt pas ses parents, `/root`, un groupe global ou un chemin
absolu afin de découvrir une dépendance. Elle expose une méthode de
configuration, des propriétés typées ou un constructeur de composition
explicite. L'absence d'une dépendance requise échoue immédiatement en
développement et dans les tests de chargement.
### Durées de vie
Trois portées sont distinguées :
1. **Application** : réglages, localisation, adaptateurs d'entrée et d'audio,
horloge de plateforme, accès au stockage et liste des sauvegardes. Ils vivent
tant que la racine existe.
2. **Campagne** : `CampaignState`, progression de mission et cas d'usage liés à
une partie. Ils sont créés lors d'une nouvelle partie ou d'un chargement et
détruits lors de sa fermeture ou de son remplacement.
3. **Plongée** : `ResolvedDiveRules`, `DiveSession`, acteurs, adaptateurs
physiques et service sonar courant. Ils sont créés au départ d'une plongée
et détruits après production de son résultat immuable.
Un objet de portée longue peut fournir un port à une portée plus courte. Il ne
conserve pas en retour une référence implicite vers un acteur ou une scène déjà
détruite. Les connexions de signaux suivent la même propriété et sont retirées
avec leur portée.
Les services d'infrastructure ne deviennent pas propriétaires des règles. Par
exemple, le dépôt de sauvegarde écrit un document demandé par un cas d'usage ;
il ne décide pas quand une mission progresse ou combien d'argent est gagné.
### Navigation et communications
La présentation traduit une interaction en commande ou appel de cas d'usage.
Elle consomme ensuite un résultat, un snapshot ou un événement typé. Elle ne
modifie jamais directement l'état de campagne ou un acteur du monde.
Les événements locaux peuvent utiliser les signaux Godot entre objets déjà
reliés par la composition. Un signal décrit un fait passé et ne sert pas de
canal global anonyme. Une intention reste une commande nommée.
Chaque écran et composition de plongée peut être instancié dans un test avec de
faux ports, sans charger une campagne réelle, le stockage de la machine ou tous
les autres écrans.
### Évolution
Tout nouvel autoload, accès statique global ou exception durable à cette
politique exige un ADR qui précise son propriétaire, sa durée de vie, son API,
sa stratégie de réinitialisation et son isolation en test.
## Conséquences
- Les dépendances et leur durée de vie sont visibles à l'endroit où les scènes
sont assemblées.
- Un changement d'écran ne détruit pas les services applicatifs nécessaires à
la session.
- Une nouvelle campagne peut remplacer proprement la précédente sans conserver
ses signaux, caches ou acteurs.
- Les écrans sont testables avec des doubles légers et ne dépendent pas d'un
ordre d'initialisation d'autoloads.
- La composition contient davantage de code d'assemblage explicite et doit
rester distincte des règles de gameplay.
- Les transitions doivent libérer leurs objets de portée courte et seront
couvertes par des tests d'intégration et de fuite de nœuds.
- Les fonctionnalités transversales passent par des ports nommés plutôt que par
une commodité globale.
## Alternatives considérées
- **Un autoload par service** : rejeté, car sauvegarde, campagne, audio et
navigation deviendraient accessibles et mutables depuis toutes les couches.
- **Un unique `GameManager` autoload** : rejeté, car il concentrerait
assemblage, navigation et règles dans un propriétaire générique.
- **Un bus d'événements global** : rejeté, car producteurs et consommateurs
seraient reliés par des noms implicites et des durées de vie difficiles à
vérifier.
- **Remplacer toute la scène courante à chaque écran** : rejeté comme
navigation principale, car les services persistants devraient alors être
globaux ou reconstruits avec un état dupliqué.
- **Rechercher les services dans les parents ou par groupe Godot** : rejeté,
car la dépendance resterait cachée et dépendrait de la forme de l'arbre.
@@ -0,0 +1,163 @@
# 0009 — Progression pilotée par les connaissances et actions du joueur
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
L'entreprise qui engage le joueur sait qu'elle poursuit un trésor, mais présente
la première mission comme la récupération du travail du précédent opérateur.
Le monde possède donc une vérité, le commanditaire possède des intentions et le
joueur ne dispose au départ que d'une version partielle ou mensongère.
Une épave peut être localisée, identifiée, examinée, ouverte et revisitée sans
que son objectif principal ait été sécurisé. Débloquer la suite dès la
découverte du navire rendrait la récupération de l'objet ou de l'indice
facultative et contredirait la boucle de campagne. À l'inverse, utiliser les
secrets du contenu pour colorer une icône ou construire un objectif visible
révélerait indirectement une information que le joueur n'a pas encore obtenue.
## Décision
### Trois niveaux d'information
Le modèle sépare explicitement :
1. **La vérité du contenu** : identité réelle d'une épave, objets présents,
chronologie, motivations et liens narratifs. Elle réside dans les définitions
de contenu et n'est jamais envoyée telle quelle à la présentation.
2. **Les termes visibles du contrat** : `ContractTerms` contient uniquement le
commanditaire affiché, les informations communiquées au joueur, l'objectif
officiel, le paiement et les prérequis annoncés.
3. **Les connaissances du joueur** : `MissionProgress` contient uniquement les
faits observés, révélations acquises, objectifs connus, épaves localisées,
identités établies et activités découvertes.
Les intentions cachées de l'entreprise et les véritables étapes de progression
sont décrites dans des `CampaignObjectiveDefinition` distinctes de
`ContractTerms`. Une définition peut rester invisible, devenir connue après une
révélation ou n'exister que comme règle interne reliant des faits.
La sauvegarde persiste les identifiants et états nécessaires à la progression,
mais l'application expose à chaque écran une projection filtrée par les
connaissances du joueur. Le fait qu'une donnée existe dans le contenu ou le
format de sauvegarde ne l'autorise pas à traverser le contrat de présentation.
### Faits, objectifs et révélations
Un fait est un événement métier idempotent déjà établi, par exemple :
- une zone d'écho a été obtenue ;
- une épave a été localisée ;
- une preuve a été observée ;
- une identité a été établie ;
- un accès a été ouvert ;
- un objet se trouve sur le site, dans le panier, dans la cale ou a été remis ;
- des données récupérées ont été analysées.
Une révélation est une nouvelle connaissance produite par une commande validée
et ses prérequis, pas un texte déclenché par l'interface. Elle peut rendre
visible un objectif, une activité, une identité ou une nouvelle zone probable.
Un `CampaignObjectiveDefinition` exprime des prérequis typés et un effet. La
progression les évalue après les commandes applicatives. Elle ne dépend ni de
la scène ouverte, ni d'un libellé, ni d'une comparaison effectuée dans le HUD.
Les mêmes faits ne produisent une révélation qu'une seule fois.
Une hypothèse de joueur, telle que la cible présumée dans le dossier, reste
révisable et n'est pas un fait d'identification. Elle ne valide ni le contrat,
ni une étape cachée.
### Localisation distincte de l'accomplissement
Localiser ou identifier une épave ne satisfait pas automatiquement l'objectif
qui y conduit. Chaque étape requiert le dernier fait métier prévu par sa
définition : récupération, sécurisation dans la cale, analyse, lecture, remise
ou autre action explicitement nommée.
Pour la première mission de la verticale :
1. le contrat visible demande de retrouver le navire de prospection et son
module de cartographie ;
2. la localisation rend l'épave revisitable, sans révéler la piste suivante ;
3. l'identification confirme le navire, sans terminer la mission ;
4. les objets placés dans le panier restent provisoires ;
5. le treuillage puis `EmptyBasket` sécurisent le module et ses données dans la
cale ;
6. `AnalyzeRecoveredData` révèle la zone probable du U-Boot ;
7. la remise physique du module et le paiement restent des actions séparées.
La copie analysable des données demeure acquise après son transfert, même si le
module physique est remis ensuite. Les intentions réelles de l'entreprise ne
deviennent visibles que par une révélation narrative prévue, pas par le simple
accomplissement technique d'un objectif caché.
### Projection de la carte
La carte globale reçoit une projection de `MissionProgress` et applique les
états suivants :
- une vérité de contenu non localisée n'est pas affichée comme épave ;
- une zone d'écho connue reste une zone approximative ;
- une épave localisée sans identité établie est nommée
`Épave non identifiée` ;
- une épave identifiée utilise le nom du navire ;
- l'icône est colorée lorsqu'au moins une activité connue et réalisable reste à
accomplir ;
- l'icône devient monochrome lorsque toutes les activités connues sont
terminées et qu'aucun objectif connu n'y demande une action.
Une activité cachée ne colore pas l'icône : cela divulguerait son existence.
Lorsqu'une révélation la rend connue, la projection peut redevenir colorée.
Cette couleur est une information de présentation dérivée d'un état métier
filtré ; elle n'est pas sauvegardée comme vérité indépendante.
### Responsabilités
Le domaine possède faits, prérequis, états d'objets et transitions de
progression. L'application exécute les commandes, publie les révélations et
construit les projections autorisées. Le monde produit uniquement des
observations et interactions. La présentation affiche les projections et émet
des commandes ; elle ne lit jamais directement les définitions cachées.
Les tests de contenu vérifient au minimum :
- que le contrat visible ne référence pas une révélation prématurée ;
- que chaque objectif de campagne possède une suite de prérequis atteignable ;
- que la campagne reste terminable sans le pack d'archives facultatif ;
- que localiser ou identifier seul la première épave ne révèle pas le U-Boot ;
- que l'analyse des données sécurisées le révèle une seule fois ;
- que chaque leurre possède des faits cohérents et une contradiction
découvrable ;
- qu'aucune projection ne contient un fait ou une activité encore caché.
## Conséquences
- L'entreprise peut mentir sans dupliquer une seconde mission ou risquer que
l'interface expose son véritable objectif.
- Une épave découverte reste facile à retrouver, mais sa découverte ne court-
circuite pas la récupération et l'analyse.
- La carte peut revenir sur une épave lorsqu'une nouvelle activité y est
révélée, sans modifier son historique d'exploration.
- Ajouter une révélation exige des prérequis typés et un test de contenu, ce qui
augmente le travail d'auteur mais réduit les blocages narratifs.
- Les écrans reçoivent des projections dédiées plutôt que les ressources de
contenu complètes.
- Les états de couleur et les libellés de carte restent dérivés et ne créent
pas une source de vérité concurrente.
## Alternatives considérées
- **Mettre objectifs visibles et cachés dans `ContractTerms`** : rejeté, car un
écran ou un export de contrat pourrait révéler les intentions de l'entreprise.
- **Utiliser des booléens globaux sans propriétaire** : rejeté, car leur origine,
leur ordre et leurs prérequis seraient implicites.
- **Débloquer la suite à l'identification de l'épave** : rejeté, car l'objet ou
l'indice demandé deviendrait facultatif.
- **Laisser l'UI déduire l'objectif suivant** : rejeté, car la présentation
calculerait une règle de campagne.
- **Colorer une épave tant qu'elle contient un secret quelconque** : rejeté,
car la carte divulguerait les activités inconnues du joueur.
- **Valider immédiatement la cible présumée** : rejeté, car l'hypothèse doit
rester révisable et la déduction ne doit pas devenir un quiz d'interface.
@@ -0,0 +1,99 @@
# 0010 — Bathymétrie à surface unique pour la V1
- Statut : accepté
- Date : 2026-08-10
- Remplace : aucun
## Contexte
Le sonar retenu représente le fond par une grille bathymétrique continue. Pour
une ligne et une colonne données, cette structure ne peut stocker qu'une seule
élévation autoritaire. Elle convient aux pentes, crêtes, chenaux, dépressions et
reliefs ouverts, mais pas à un tunnel, une arche ou une grotte possédant
plusieurs surfaces superposées au même emplacement horizontal.
Une représentation hybride pourrait combiner plusieurs couches, fragments de
maillage, points volumétriques ou voxels locaux. Aucun site de la verticale
n'exige encore cette complexité : les véritables grottes à plusieurs niveaux et
les surplombs complexes sont hors périmètre de la V1.
## Décision
Le terrain navigable et les informations critiques du sonar de la V1 utilisent
une bathymétrie à surface unique. Chaque cellule valide du scan possède au plus
une hauteur de terrain reconstruite.
Les sites de la verticale sont conçus de sorte que :
- les routes nécessaires puissent être comprises depuis une carte de hauteur ;
- aucun objectif n'exige de passer sous un surplomb naturel ou dans un tunnel ;
- aucune preuve indispensable ne soit cachée par une seconde surface au même
emplacement horizontal ;
- une absence de mesure reste inconnue et ne soit pas comblée artificiellement ;
- les limites de la zone jouable ne soient pas présentées comme un relief à
interpréter.
Les épaves, le sous-marin, le navire et les accessoires restent de véritables
modèles 3D pour le rendu et les collisions. Une épave peut présenter des
ouvertures, des volumes, des éléments dressés et des zones interactives. Le
sonar de la V1 n'a toutefois pas à reconstruire précisément son intérieur, son
dessous ou plusieurs ponts superposés.
Les surfaces anormales et les épaves peuvent être signalées par des
`SonarContact` distincts et par leur contribution visible à la surface
extérieure. Une interaction, une observation visuelle ou un indice du dossier
confirme ensuite leur nature. Le renderer sonar ne fabrique pas un volume
intérieur pour compenser les limites de la bathymétrie.
Un surplomb purement décoratif reste autorisé s'il n'est ni une route, ni un
abri nécessaire, ni le support d'une information de mission et si son absence
ou sa simplification sur le sonar ne trompe pas le joueur. Cette exception est
vérifiée lors de la revue du site et des tests humains.
Aucune interface générique pour plusieurs couches, aucun voxel local et aucun
champ de distance ne sont ajoutés préventivement. Le contrat du scan conserve
les valeurs réelles et les contacts nécessaires, sans prétendre résoudre un
besoin futur non validé.
### Déclencheur d'une évolution
Un prototype de représentation hybride devient nécessaire seulement lorsqu'un
site retenu exige au moins l'un des comportements suivants :
- naviguer au-dessus et au-dessous de surfaces superposées ;
- lire au sonar l'entrée et la continuité d'une grotte ou d'un tunnel ;
- distinguer plusieurs ponts, plafonds ou niveaux comme information de jeu ;
- représenter une cible indispensable qu'une carte de hauteur rend ambiguë ou
invisible.
Ce prototype comparera au minimum couches bathymétriques locales, fragments de
maillage et représentation volumétrique bornée. Son adoption, ses données et
son coût de persistance nécessiteront un nouvel ADR. Elle ne modifiera pas
silencieusement le contrat de la V1.
## Conséquences
- La reconstruction sonar reste continue, compacte et compatible avec le
langage visuel validé par le POC B.
- Le level design de la verticale doit éviter les configurations que le sonar
ne peut pas communiquer honnêtement.
- Les épaves peuvent conserver une géométrie visuelle riche sans imposer un
sonar volumétrique.
- Certaines formes 3D seront simplifiées ou absentes de la carte sonar ; elles
ne peuvent pas porter seules une information critique.
- Les budgets de la tranche sonar ne comprennent ni voxelisation, ni
reconstruction multi-couche, ni navigation dans une grotte.
- Une future évolution dispose de déclencheurs concrets et devra prouver sa
lisibilité avant d'élargir le format de scan.
## Alternatives considérées
- **Construire immédiatement un sonar hybride** : rejeté, car aucun objectif de
la verticale ne justifie encore son coût de données, de rendu et de test.
- **Utiliser des voxels pour tous les sites** : rejeté, car les essais du POC B
ont montré une lecture plus confuse que la surface bathymétrique continue.
- **Aplatir silencieusement une grotte complexe dans la carte** : rejeté, car le
sonar donnerait une information trompeuse au joueur.
- **Interdire toute géométrie 3D complexe aux épaves** : rejeté, car la limite
concerne la représentation sonar et le level design critique, pas les modèles
visuels ou les collisions locales.
@@ -0,0 +1,45 @@
# 0011 — Godot 4.7.2 comme baseline de production
- Statut : accepté
- Date : 2026-10-02
- Remplace : aucun
## Contexte
La verticale de production a démarré sous Godot 4.7.1 et la barrière qualité
refusait toute autre version. L'environnement de développement disponible
fournit désormais Godot 4.7.2. Conserver l'exigence exacte 4.7.1 empêche
d'exécuter la barrière alors que cette mise à jour corrective reste dans la
même branche 4.7 et ne change ni le renderer Forward+ ni les contrats de jeu.
Les POC A et B restent des références historiques figées sous Godot 4.7.1 et
GL Compatibility. Leur baseline n'est pas réécrite rétroactivement.
## Décision
Godot 4.7.2 stable devient la version exacte exigée pour le projet de
production et sa barrière qualité. Les documents de production et le commentaire
du projet sont alignés sur cette version.
La barrière continue de comparer la version exacte au préfixe officiel attendu.
Une version antérieure, une version corrective ultérieure ou une préversion est
refusée jusqu'à une nouvelle décision explicite.
## Conséquences
- La barrière s'exécute avec l'installation Godot 4.7.2 disponible.
- Les résultats de la verticale sont reproductibles sur une version moteur
unique.
- La mise à jour doit être validée par le chargement de toutes les ressources,
les tests unitaires et les tests d'intégration existants.
- Les baselines des POC continuent de documenter la version 4.7.1 avec laquelle
elles ont été validées.
## Alternatives considérées
- **Rester sur Godot 4.7.1** : rejeté, car cette version n'est plus
l'installation disponible pour poursuivre la verticale.
- **Accepter toute version 4.7.x** : rejeté, car une plage implicite rendrait
les validations moins reproductibles.
- **Supprimer le contrôle de version** : rejeté, car il masquerait les mises à
jour du moteur au lieu de les valider explicitement.