Un article précédent parlait de ce qui fait revenir les joueurs d'un idle game, en théorie. Celui-ci parle d'une seule brique concrète de Cendrelune qui sert exactement ça : une notification, une seule, qui prévient quand une réserve va atteindre son plafond pendant que le joueur n'est pas là. Pas de serveur, pas de campagne de notifications, pas de tableau de bord marketing — un calcul, programmé au moment où l'app passe en arrière-plan, et annulé dès qu'elle revient.

Pas de serveur, juste un calcul

La production de ressources dans Cendrelune est idle : elle continue, en théorie, que l'app soit ouverte ou non, et le rattrapage se fait au retour en calculant depuis le dernier tick enregistré. La fonction nextStorageFull() réutilise ce même calcul pour répondre à une question différente : à ce rythme de production et avec ce plafond de stockage, dans combien de temps telle ressource va-t-elle déborder ? Comme le calcul dérive uniquement de l'état du jeu et non d'un minuteur qui tourne en tâche de fond, il reste exact que l'app soit fermée depuis cinq minutes ou cinq heures — il n'y a rien à faire tourner pour que la prévision reste juste.

Le rappel est programmé dans _programmerRappel(), appelé au moment où le joueur quitte l'app. Il est annulé dans handleResume(), dès que le joueur revient — que la notification ait sonné entre-temps ou non. Il n'y a jamais qu'un seul rappel programmé à la fois : en programmer un nouveau remplace systématiquement le précédent, via un identifiant fixe côté flutter_local_notifications.

La fenêtre entre trop tôt et trop tard

Le calcul peut renvoyer n'importe quel délai, de quelques secondes à plusieurs mois selon la ressource et le moment de la partie. Deux bornes filtrent ce résultat avant de programmer quoi que ce soit. En dessous de 30 minutes, aucune notification n'est programmée : en tout début de partie, les entrepôts se remplissent en quelques minutes, et prévenir à ce rythme reviendrait à couper les notifications dans les réglages système — ou à désinstaller. Au-delà de 3 jours, la prévision est jugée trop lointaine pour avoir un sens : le joueur aura rouvert l'app entre-temps, et une alerte programmée aussi loin à l'avance n'est plus qu'un bruit de fond qui arrivera au mauvais moment.

« Une notification n'est utile que si elle sait attendre : ni trop tôt, pour ne pas devenir un bruit de fond qu'on coupe, ni trop tard, pour ne pas annoncer un état de jeu qui n'existera plus. »

Un bug que le commentaire raconte encore

Le code garde la trace d'une version antérieure qui ne faisait pas ce calcul ressource par ressource. Elle cherchait d'abord la ressource dont la saturation était la plus proche, puis appliquait la fenêtre de 30 minutes à 3 jours sur ce seul résultat — et abandonnait toute l'alerte s'il tombait en dehors. Le problème, c'est qu'en partie avancée, il y a presque toujours une ressource au plafond ou à quelques minutes de l'être : le bois, en général. Cette ressource-là gagnait systématiquement la course à « la plus proche », se faisait rejeter parce que trop proche, et aucune autre ressource — même une qui aurait été dans la bonne fenêtre — n'était jamais examinée. Le correctif inverse l'ordre : chaque ressource productrice est évaluée dans la fenêtre, indépendamment des autres, et la première qui la respecte est retenue. Ni le git log ni les notes de dev ne disent quand ce changement a eu lieu ; seul le commentaire resté dans storage_forecast.dart explique pourquoi il a fallu le faire.

Ne jamais promettre un rappel qui n'arrivera pas

L'option est désactivée par défaut. L'activer déclenche une demande de permission système — jamais l'inverse : Cendrelune ne demande rien au lancement, seulement au moment où le joueur choisit explicitement d'allumer les rappels dans les réglages. Si la permission est refusée, l'option reste éteinte plutôt que de rester affichée comme active alors qu'aucune notification n'arrivera jamais.

Sur Android, la programmation utilise un mode explicitement inexact (inexactAllowWhileIdle) : un décalage de quelques minutes ne change rien à un rappel de confort sur un jeu idle, et ça évite de réclamer la permission d'alarme exacte qu'Android 12 impose pour les rappels programmés au tick près — disproportionnée pour ce cas d'usage. Le canal de notification Android, lui, est créé avant qu'un contexte d'interface n'existe dans l'app ; ses deux libellés système restent donc en anglais, faute de pouvoir être localisés à ce stade, mais ils ne sont de toute façon visibles que dans les réglages système du téléphone, jamais dans le jeu.

Il n'y a aucun package d'analytics dans le projet, et donc aucune mesure de combien de joueurs reviennent grâce à ce rappel plutôt que de leur propre initiative. C'est un pari sur ce qui semble juste pour ce type de jeu, pas un chiffre qu'on pourrait citer.

Ce qui n'existe que dans Cendrelune

AbcDonjon n'a pas cette fonctionnalité, et n'en a pas besoin : c'est un jeu de mots joué par sessions, sans production qui continue en arrière-plan ni réserve qui déborde. La dépendance flutter_local_notifications n'apparaît d'ailleurs pas dans son pubspec.yaml. Ce n'est pas un choix de priorisation entre les deux jeux — c'est simplement que le mécanisme n'a de sens que pour l'un des deux.

Ce que ce petit système raconte, au fond, c'est qu'une notification n'a pas besoin d'un serveur, d'une campagne ou d'un moteur de règles pour être pertinente. Elle a besoin de savoir attendre la bonne fenêtre, de ne jamais mentir sur ce qu'elle promet, et de disparaître dès qu'elle n'a plus de raison d'être là.