Files
Ransom-s-wake/docs/adr/0008-composition-owned-service-lifetimes.md

129 lines
6.1 KiB
Markdown

# 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.