The Circle
Une plateforme où chaque communauté de joueurs se fabrique ses tournois, sa monnaie et ses cartes à collectionner — et que j'exploite seul, avec de vrais utilisateurs.
Les communautés de jeu vivent sur Discord, où tout finit par se perdre : les résultats de tournois, les classements, qui a gagné quoi, ce qu'on s'était promis la semaine dernière. The Circle leur donne un endroit à elles. Chaque communauté arrive avec ses membres, et repart avec sa monnaie, ses tournois, sa collection de cartes, sa boutique et son classement — le tout cloisonné : ce qui se passe chez l'une n'existe pas chez l'autre.
C'est mon plus gros projet : 139 écrans, une base de 143 tables, et une exploitation au quotidien. Le code n'est qu'une partie du travail ; le reste, c'est tenir un service que des gens utilisent le soir, répondre à leurs demandes, et régler les incidents quand ils arrivent.
Ce que la plateforme fait
Tournois
Brackets, double élimination, matchs 1 contre 1, équipes équilibrées par niveau, et des tournois ouverts à plusieurs centaines de joueurs, avec check-in, appel de match et preuve par capture d'écran.
Économie
Une monnaie par communauté, une banque qui sérialise chaque opération dans un grand livre, des quêtes, un loto hebdomadaire, et un journal de toutes les actions du staff.
Cartes à collectionner
Un studio de création avec pinceaux et calques, des raretés, des matériaux animés, des packs, et un marché d'échange entre joueurs.
Assistant
Un assistant qui répond aux questions des membres avec les vraies données de leur communauté — et qui continue de répondre quand l'IA est indisponible.
Discord
Salons et rôles synchronisés, un bot permanent qui accueille, récompense le temps passé en vocal, gère le check-in et surveille le site.
Diffusion et modération
Des incrustations pour les streamers, l'analyse des images à l'arrivée, des signalements discrets, des quotas, et un espace d'administration pour ce qui remonte.
Un tournoi de 500 joueurs sans arbitre debout à 23 h
Le chantier de la rentrée : un tournoi 1 contre 1 ouvert à cinq cents participants, où toute la chaîne — inscription, check-in, appel de match, preuve, litige, arbitrage — doit tourner sans intervention humaine le soir même.
- Lire la preuve de chaque manche. Le joueur envoie la capture de fin de partie, et la plateforme la lit. L'ancienne lecture, en plein écran, laissait passer un score gonflé une fois sur deux. La nouvelle repère les barres de couleur de l'en-tête et lit chaque cellule à une position relative — aucune coordonnée fixe, les écrans vont de 640 à 2 048 pixels de large. Trois lectures indépendantes sont croisées avant de lever un doute. Testée sur dix vraies captures du clan, dont certaines volontairement hors règles : onze verdicts sur onze conformes.
- Ne jamais refuser seul. Le contrôle ne rejette pas une preuve : il envoie le match à l'arbitrage avec la capture et ce qu'il a lu. Écarter automatiquement un vrai vainqueur à 23 h serait pire que le problème qu'on cherche à régler.
- Voir ce que fait la machine. Une page de santé montre le volume de lectures, le taux de doute, le temps de lecture, la file d'attente et les derniers échecs avec leur vignette. Un quota freine celui qui enverrait des centaines d'images.
- Un blocage que rien ne signalait. En vérifiant l'événement directement en base, j'ai découvert que ses quatre phases n'avaient aucune date — et qu'aucun écran ne permettait d'en saisir. Rien ne plantait, mais aucune soirée n'aurait été annoncée, et le tableau ne se serait jamais terminé. Un écran de saisie calcule désormais les huit dates depuis le coup d'envoi, en heure locale pour résister au changement d'heure, et une contrainte en base refuse une fin antérieure au début.
- Un règlement relu contre d'autres. J'ai comparé le règlement à ceux de tournois amateurs équivalents, et fermé cinq silences — dont un qui aurait produit un litige impossible à trancher dès la première soirée. En une ou deux lignes chacun : le texte avait été raccourci exprès pour être lu en entier.
Un assistant qui répond même sans IA
L'assistant des communautés dispose de vingt-cinq outils : solde, historique de monnaie, tournois, classements, boutique, règlement. Pour les questions ouvertes, j'ai refusé de laisser le modèle écrire du SQL : la base contient les données de toutes les communautés. Il compose à la place une requête guidée — une source parmi dix-sept, des filtres, un regroupement — et c'est le serveur qui l'exécute en imposant lui-même le cloisonnement. Tous les calculs se font côté serveur ; le modèle ne fait que rédiger.
Le quota gratuit du fournisseur est tombé à vingt requêtes par jour pour tout le projet. Plutôt que de laisser l'assistant muet, j'en ai construit un second, sans modèle de langage, qui reconnaît les questions courantes même mal écrites.
- Partir des vraies questions. 135 questions posées depuis avril, analysées une à une : trente-huit types de questions, avec leurs formulations réelles.
- Comprendre « ki a le + de kill ». Normalisation de l'argot, correction des fautes de frappe à partir de cinq lettres — en dessous, « vers » devenait « sers » —, et score des mots pondéré par leur rareté.
- Un dictionnaire de sens de 6 Mo. Un modèle de similarité de phrases classique pesait trop lourd pour une fonction serverless. J'ai pris un modèle « statique », une simple table de vecteurs, et je l'ai réduit de 434 Mo à 6 Mo en gardant le vocabulaire utile. Vérifié contre le modèle complet : corrélation de 0,96.
- Le résultat. 99 % de bonnes intentions sur les vraies questions, 66 réponses « sûres » justes sur 66, aucune question hors sujet prise pour sûre, et des réponses en moins de 600 ms.
L'IA payante est revenue en tête de chaîne, avec un plafond de 5 € par mois tenu en base : chaque appel est enregistré avec son coût, et le budget du jour est le reste du mois divisé par les jours restants. Viennent ensuite deux fournisseurs gratuits, puis l'assistant sans IA. Coût mesuré en production : environ 0,007 $ par question quand le cache est chaud. En testant chaque étage, j'ai découvert que l'un des replis n'avait jamais répondu : le modèle configuré était bridé à zéro requête sur la clé gratuite.
Un bot Discord sur un Raspberry Pi
Le bot reste connecté en permanence, gratuitement, sur un Raspberry Pi préparé entièrement depuis le PC : image système vérifiée, carte écrite par script, premier démarrage configuré à distance, service qui redémarre tout seul.
- Un observateur sans clé de base. Le bot ne parle qu'à des routes du site protégées par une clé limitée. Une fuite de la machine ne donne accès à aucune donnée, et si le bot s'éteint, les boutons et les commandes continuent de marcher.
- Ne jamais payer deux fois. Le temps passé en vocal est récompensé dans la monnaie de la communauté. Un bot qui redémarre ou tourne sur deux machines aurait pu créditer deux fois : il envoie des numéros de minute, que la base dédoublonne avant de ne verser que la différence.
- Un loto tiré par la base. Achat de tickets, tirage pondéré sans remise dans une transaction, report ou remboursement sous le seuil de joueurs. Un test écrit avant la mise en ligne a évité un tirage le mauvais jour : deux fonctions ne comptaient pas les jours de la semaine à partir du même.
- Le Pi surveille le site. Toutes les quinze minutes : la page d'accueil vue de l'extérieur, la base, la lecture des captures, les tâches planifiées, les quotas. Une alerte n'est envoyée qu'après un second essai, une seule fois par panne, avec un rappel toutes les six heures.
- Mesurer sa marge. Test de charge avec les vrais modules du bot et de faux événements : cinq fois la charge d'un tournoi de 500 joueurs tiennent sur un quart d'un cœur. Reprise en quinze secondes après un plantage.
Exploiter, c'est surtout diagnostiquer
- Une alerte qui s'emballait. 111 mails d'alerte en une journée, quota d'envoi dépassé. L'alerte partait à chaque passage du vert au rouge — juste pour une tâche quotidienne, faux pour une tâche à la minute qui échoue par intermittence. Elle attend désormais trois échecs de suite, et n'envoie qu'un mail par tâche et par jour.
- Une hypothèse fausse, réfutée par la mesure. Des erreurs de délai intermittentes semblaient venir d'une base saturée. Une tâche témoin est restée stable à 40 ms pendant tout l'incident, et aucun échec ne durait moins de 5 035 ms : c'était un délai par défaut de cinq secondes, appliqué à un appel conçu pour en durer quarante-cinq.
- Une base qui ne rendait pas sa place. Un journal interne pesait 58 Mo pour 366 lignes, et les compteurs affichaient zéro ligne — justement parce que le nettoyage automatique, qui se déclenche sur ces compteurs, n'était jamais passé. Base ramenée de 207 à 144 Mo sans toucher une donnée, puis entretien planifié chaque semaine.
- Des déploiements trop lourds. Le quota de l'hébergeur se remplissait : chaque déploiement embarquait trois modèles de détection d'images alors qu'un seul servait. Import ciblé, bundle partagé passé de 43 à 7 Mo.
- Sortir un produit de la base partagée. A.M.C Bank vivait dans la même base. Avant de l'en retirer — 37 tables, 117 fonctions, 15 tâches planifiées —, j'ai prouvé que la sauvegarde se restaurait en la rechargeant entièrement dans une transaction annulée. Puis suppression, sans toucher une ligne de la plateforme.
La sécurité, vérifiée fonction par fonction
En livrant l'assistant, j'ai repéré que le grand livre de la monnaie était lisible sans compte. J'ai élargi la vérification à toutes les fonctions de base ouvertes aux visiteurs : 102, triées en quatre familles. Vingt-sept écrivaient sans vérifier qui les appelait ; elles sont désormais réservées au serveur, sauf deux qu'un membre connecté appelle légitimement. Chaque fermeture est livrée avec ses tests, rejoués dans une transaction annulée, et un garde-fou qui fait échouer la migration si une porte reste ouverte — ou si un appelant légitime perd son accès.
La nouvelle règle d'accès du grand livre, écrite naturellement, prenait 5,6 secondes pour compter les mouvements d'une grosse communauté : la base rappelait la fonction de contrôle sur chacune des 41 000 lignes. Réécrite pour être calculée une seule fois, elle répond en 20 ms.
Ce qui reste difficile
- Cloisonner sans se tromper. Les points, les cartes et les tournois appartiennent à une communauté ; l'identité et les objets cosmétiques appartiennent au joueur, partout. Cette frontière traverse cent quarante-trois tables, et elle est tenue par des règles écrites dans la base, pas par des conditions dans les écrans.
- Faire évoluer un schéma pendant qu'on s'en sert. 330 migrations versionnées, sur une base utilisée le soir même. Aucun fichier déjà appliqué n'est modifié : un correctif est une nouvelle migration, avec sa raison en commentaire. Un inventaire a montré que le registre des migrations appliquées ne couvrait qu'une partie du dépôt : dix modifications notées « à appliquer » étaient déjà en production. Depuis, je vérifie en base plutôt que dans les notes.
- Tenir la charge d'une soirée. Toutes les opérations bancaires d'une communauté passent par un seul verrou. Un soir de lancement, une boucle qui créditait les joueurs un par un a mis toute la soirée en file d'attente. Depuis, un ajustement de masse est une transaction, jamais une boucle d'appels.
- Les fuseaux horaires. La saisie se fait dans le fuseau de la communauté, l'affichage dans celui du visiteur, et une journée de quête se termine à minuit chez le joueur.
Une fonctionnalité qu'il faut expliquer en message privé n'est pas finie.
Ce que j'en retiens
Construire pour d'autres que soi change tout : une règle de jeu mal expliquée coûte plus cher qu'un bug. J'écris la documentation en même temps que la fonctionnalité, et je cadre les gros chantiers avec une maquette et des questions avant d'écrire du code.
C'est aussi le projet qui m'a appris à me méfier de mes propres certitudes. Plusieurs fois, la cause que j'avançais était plausible et fausse, et c'est une mesure de cinq minutes qui l'a démontée. Quand la monnaie a une valeur aux yeux des joueurs, une faille n'est plus théorique : les règles d'accès vivent dans la base, et chacune est testée sur ce qu'elle doit refuser.