Cendrelune et AbcDonjon ont été commencés à neuf jours d'intervalle : le premier commit de l'un date du 13 juin 2026, celui de l'autre du 22 juin. Deux jeux Flutter, écrits par la même personne, dans le même éditeur, sur la même machine. Leurs dossiers lib/ font aujourd'hui presque exactement la même taille — 12 649 lignes pour Cendrelune, 13 173 pour AbcDonjon, en excluant les fichiers de traduction générés.

Et pourtant, ouverts côte à côte, on croirait deux équipes différentes. La comparaison qui suit ne cherche pas à trancher lequel des deux a raison, mais à voir ce qu'elle montre — y compris là où elle ne montre rien de flatteur.

Deux arborescences qui n'ont rien en commun

Cendrelune est rangé en clean architecture feature-first. Ce n'est pas une reconstitution a posteriori : le message du tout premier commit du dépôt s'appelle littéralement « Initial commit — Cendrelune en clean architecture feature-first ». L'intention était donc posée avant la première ligne de gameplay. Le résultat, ce sont 103 fichiers Dart répartis en trois racines — lib/app pour l'amorçage et le routeur, lib/core pour le thème, les icônes et l'observabilité, et lib/features pour neuf fonctionnalités : achievements, barracks, expedition, game, hamlet, monetization, notifications, review, settings. Chacune se subdivise à son tour en domain, data et presentation. Il n'est pas rare d'y descendre à six niveaux de profondeur avant d'atteindre un fichier.

AbcDonjon tient dans sept dossiers à plat : daily, data, engine, models, monetization, observability, ui. Deux niveaux de profondeur au maximum, et 52 fichiers Dart — la moitié de Cendrelune, pour un volume de code équivalent. Mécaniquement, ses fichiers pèsent donc en moyenne deux fois plus lourd.

Riverpod d'un côté, rien du tout de l'autre

La différence ne s'arrête pas au rangement. Cendrelune utilise flutter_riverpod — la dépendance apparaît dans vingt fichiers — plus go_router pour la navigation, et ne contient aucun ChangeNotifier. Cinq interfaces abstraites isolent le domaine du monde extérieur : GameRepository, GameServicesRepository, EntitlementsRepository, NotificationScheduler, ReviewPrompter. Neuf cas d'usage nommés (RecruitTroop, LaunchExpedition, ApplyIdleProgress, UpgradeBuilding…) portent les règles métier hors de l'interface.

Le pubspec.yaml d'AbcDonjon, lui, ne contient aucun package de gestion d'état et aucun routeur : ni Riverpod, ni Provider, ni Bloc, ni go_router. Trois ChangeNotifier écrits à la main tiennent tout l'état du jeu — le contrôleur de partie, la méta-progression, et le statut premium — et la navigation passe par le Navigator de Flutter. C'est le Flutter le plus nu qu'on puisse écrire.

Le paradoxe des tests

C'est en regardant les dossiers test/ que la comparaison devient contre-intuitive. Le projet le plus léger en architecture est de loin le mieux testé : AbcDonjon compte 68 fichiers de test pour 8 361 lignes, quand Cendrelune en compte 42 pour 3 900. Plus du double de lignes de test, pour un dossier lib/ de taille équivalente et deux fois moins de fichiers.

Leur nature diffère autant que leur volume. Les tests d'AbcDonjon s'appellent balance_sim_test, generator_property_test, word_uniqueness_test, daily_determinism_test, relic_text_matches_engine_test, et il en existe un par langue de lexique (lexicon_en, lexicon_es, lexicon_it, lexicon_pt). Ce sont des tests de règles : ils interrogent le moteur sur ce qu'il produit. Ceux de Cendrelune, rangés en miroir de l'arborescence des fonctionnalités, portent des noms comme notification_wiring_test, leaderboard_wiring_test, review_prompt_wiring_test, forge_sheet_render_test, incursion_launch_sheet_test. Ce sont des tests de câblage et de rendu : ils vérifient que les morceaux se parlent et que les écrans s'affichent.

« Les tests n'ont pas suivi l'architecture. Ils ont suivi le risque. »

Ma lecture — et c'est une lecture, les dépôts donnent le fait, pas la raison — c'est que chaque suite de tests a poussé là où le jeu pouvait casser en silence. Un générateur de mots croisés peut sortir une grille insoluble ou une courbe de difficulté injouable sans qu'aucune erreur ne s'affiche : ça ne se voit qu'en interrogeant le moteur des milliers de fois. Le risque de Cendrelune est ailleurs — neuf fonctionnalités qui doivent rester branchées les unes aux autres, une notification qui doit encore se planifier après un changement de règle trois dossiers plus loin.

Ce qu'aucune des deux architectures n'a empêché

Voilà le constat le moins confortable des deux dépôts : les deux jeux ont convergé vers un gros contrôleur central, malgré des points de départ opposés.

Le game_controller.dart de Cendrelune fait 846 lignes et compte 39 import, dont 27 remontent de trois niveaux pour aller chercher six autres fonctionnalités — achievements, barracks, expedition, hamlet, notifications, review. Celui d'AbcDonjon fait 1 488 lignes avec 15 import, tous locaux.

La séparation en couches n'a donc pas empêché la centralisation. Elle l'a rendue lisible : vingt-sept chemins relatifs qui escaladent l'arborescence sont un symptôme qu'on ne peut pas ignorer, un aveu écrit en haut du fichier. AbcDonjon centralise tout autant, mais ses quinze imports locaux n'ont l'air de rien — le fichier de 1 488 lignes est le seul indice, et il ne se voit qu'en ouvrant le fichier. C'est peut-être le vrai bénéfice de la clean architecture ici : elle n'a pas rendu le code plus modulaire, elle a rendu son défaut de modularité visible depuis l'extérieur.

Là où les deux ont fini par se rejoindre

Tout ce qui n'est pas du jeu, en revanche, est presque identique dans les deux dépôts. Les deux ont une intégration continue GitHub Actions (ci.yml — celle d'AbcDonjon a été ajoutée dès le deuxième commit du projet). Les deux embarquent sentry_flutter pour la remontée de crashs, purchases_flutter pour l'achat unique via RevenueCat, in_app_review pour la demande d'avis, shared_preferences pour la persistance locale, package_info_plus, et intl avec flutter_localizations pour la traduction — dix langues pour Cendrelune, cinq pour AbcDonjon, ce dernier ayant en plus besoin d'un lexique propre à chaque langue. Les deux ont un dossier tool/ de scripts Python pour fabriquer les visuels des stores.

Autrement dit : la plomberie a convergé toute seule, sans décision d'architecture, parce que ces problèmes-là ont la même forme quel que soit le jeu. Ce sont les décisions structurantes — celles qui coûtent cher à changer plus tard — qui ont divergé.

Ce que j'en retiens, sans en faire une règle

Je me garderai de conclure qu'un des deux dépôts a raison. Ce que je constate, c'est que la forme du code a fini par ressembler à la forme du jeu. Un jeu idle, c'est plusieurs systèmes qui tournent en parallèle dans la durée et qui doivent rester cohérents entre eux : des frontières explicites et des interfaces qu'on peut remplacer par une version neutre y rendent service, et Cendrelune s'en sert réellement — sa couche de succès a une implémentation vide utilisée par défaut et dans les tests, ce dont j'ai déjà raconté l'intérêt ici. Un roguelite, c'est un moteur, une boucle, une partie qui commence et se termine : la valeur y est dans la justesse des règles, pas dans la découpe des dossiers.

Ce que les dépôts ne disent pas, en revanche, c'est le pourquoi. Un message de commit affirme une intention pour Cendrelune ; rien d'équivalent n'existe pour AbcDonjon, commencé neuf jours plus tard. Une décision d'architecture prise un soir de juin ne laisse pas toujours de trace écrite, et je préfère le dire plutôt que de reconstruire après coup un raisonnement qui aurait l'élégance de tomber juste.

Reste le fait brut, deux mois et cinq cents commits plus tard : les deux jeux sont sortis, sur l'App Store et Google Play pour l'un comme pour l'autre. Et quand on développe seul sur plusieurs projets, la seule comparaison disponible est celle qu'on peut faire soi-même, deux dépôts ouverts côte à côte, sans personne à convaincre.