- Machine de xkcd est un immense jeu de machine à billes façon Rube Goldberg, composé de dispositifs en tuiles créés par les lecteurs, qui a transformé l’idée en bande dessinée interactive réelle en seulement trois semaines
- Les expériences précédentes de projets participatifs ont conduit à un principe de conception : pour qu’un canevas partagé fonctionne bien, il faut un contexte et un objectif communs
- Pour préserver l’expressivité des joueurs tout en assurant la compatibilité des tuiles, les contraintes d’entrée et de sortie sont fortes, et chaque dispositif doit atteindre un état stable en 30 secondes
- Au lieu de simuler toute la machine en temps réel, seules les zones visibles tournent avec Rapier ; des instantanés pris au moment de l’approbation donnent l’impression que les dispositifs sont déjà en fonctionnement
- Le flux d’approbation et de déploiement des soumissions s’appuie sur React, le rendu DOM, un backend Haskell, Redis, OpenAPI, TanStack Query et une UI de modération
Le point de départ de Machine
- xkcd a publié Machine le 5 avril
- Machine est un constructeur géant de machine de Rube Goldberg, dans le style du jeu classique The Incredible Machine
- L’ensemble de la machine est constitué de petites tuiles de dispositifs créées par des lecteurs individuels de xkcd et assemblées entre elles
- L’équipe a créé Machine en 3 semaines, et l’idée est partie de Blue Ball Machine, un GIF collaboratif de 2005
- Les questions clés du brainstorming initial portaient sur l’origine des billes, l’identité de la machine vue par tout le monde, le but de la machine, les interactions des joueurs et les motivations à participer
Leçons tirées des xkcd participatifs
- Parmi les précédentes bandes dessinées interactives de xkcd centrées sur le contenu créé par les utilisateurs, Lorenz permettait aux lecteurs d’écrire le texte des cases pour faire évoluer blagues et histoires, et reste une bonne expérience
- En 2020, Collector’s Edition proposait aux joueurs de trouver des autocollants dans les archives de xkcd et de les coller une fois chacun sur un canevas global partagé, mais cela n’a pas aussi bien fonctionné qu’espéré
- Tous les joueurs commençaient au centre d’une carte vide, et un écran chaotique devenait rapidement la première impression
- Il n’y avait pas assez d’incitation à choisir soigneusement l’emplacement des autocollants, et les actions individuelles suffisaient difficilement à faire avancer un récit
- Faute d’histoire ou d’objectif partagé, il n’était pas clair comment chaque autocollant se reliait aux autres éléments de la page
- Pour qu’un canevas collectif fonctionne bien, les utilisateurs doivent pouvoir apprendre par l’exemple ce qu’il serait intéressant de créer
- Pour orienter les créations dans une même direction, il faut un contexte et un objectif partagés qui synchronisent ce que chacun cherche à construire
Conception des contraintes : expressivité, compatibilité et état stable en 30 secondes
- Même après avoir décidé de construire un grand dispositif collaboratif de chute de billes, la taille totale de la machine, la méthode de simulation et la façon d’intégrer les tuiles restaient des problèmes ouverts
- En supposant une machine de 100x100, l’objectif consistant à faire tourner 10 000 tuiles en temps réel côté client, avec des dizaines de billes par tuile, a été jugé risqué
-
Priorité à l’expressivité plutôt qu’à l’exactitude
- L’équipe a envisagé de faire tourner toute la machine sur le serveur ou de simuler chaque tuile pour la valider
- Mais le prototype d’éditeur permettait de créer facilement des motifs chaotiques de collisions de billes, ce qui a conduit à la conclusion qu’exiger une machine prévisible réduirait la liberté des joueurs
- La conception finale privilégie la flexibilité des joueurs, jusqu’à permettre des dispositifs très non déterministes ou cassés
- Ce choix a rendu nécessaire une modération active pour vérifier que les tuiles respectent les contraintes et retirer les contenus offensants
-
Contraintes d’entrée et de sortie pour la compatibilité entre tuiles
- Au départ, l’équipe a envisagé de laisser le joueur suivant étendre librement la machine en fonction de la position de sortie de la tuile précédente
- Mais si une tuile placée tôt devait ensuite être remplacée, une grande zone dépendante de cette tuile pouvait se retrouver cassée
- Ils ont donc imposé de fortes contraintes d’entrée et de sortie afin que plusieurs joueurs puissent créer des conceptions compatibles dans le même espace de tuile
- Cette approche rejoint le Robustness principle, « être conservateur dans ce que l’on envoie et libéral dans ce que l’on accepte »
- Le générateur de carte de Kevin part d’un puzzle simple à 1 entrée et 1 sortie, se complexifie avec des fusions intermédiaires à 4 entrées et 4 sorties, puis revient à 2 sorties par tuile à la fin
- L’éditeur fournit un retour en temps réel lorsque les joueurs créent une tuile
- Une tuile doit, en moyenne, envoyer des billes à une vitesse similaire à celle à laquelle elle les reçoit
- L’objectif est de réduire les dispositifs qui avalent les billes ou créent de longs délais
- Un chaos testing a été appliqué en randomisant la vitesse des billes entrant dans l’éditeur afin de refléter les variations des entrées en amont
-
Un état stable doit être atteint en 30 secondes
- Pour réduire le temps d’observation nécessaire aux modérateurs, un critère arbitraire a été fixé : un dispositif doit entrer dans un état stable en 30 secondes
- Le calcul selon lequel observer 10 000 tuiles pendant 30 secondes chacune représenterait environ 83,3 heures de modération totale a servi de référence
- Les billes ont aussi été modifiées pour expirer après 30 secondes
- Sans expiration, la première expérience des joueurs débutants devenait une situation où les billes s’accumulaient à l’écran
- Le nombre de rigid bodies actifs augmentait, ralentissant aussi la simulation physique
- L’expiration des billes empêche les erreurs de s’accumuler avec le temps et simplifie la modération, car 30 secondes d’observation suffisent à voir la plupart des positions d’arrivée des billes
Ne pas faire tourner toute la machine en temps réel
- La première grande hypothèse de l’architecture de Machine était que, si les contraintes ci-dessus étaient respectées, différentes tuiles pouvaient être assemblées pour donner l’impression d’une machine complète
- Cette hypothèse a été vérifiée en générant et en résolvant quelques petites cartes
- Comme il était impossible de faire tourner toute la machine en temps réel côté serveur ou côté client, il fallait une méthode qui ne simule que la zone autour de ce que l’utilisateur voit
- L’objectif était de permettre de suivre une bille du haut jusqu’au bas de la machine
-
Un monde physique limité à la zone visible
- Le premier visualiseur de carte simulait uniquement la zone visible, mais lors du défilement, les nouvelles tuiles qui entraient à l’écran commençaient vides, ce qui créait des ruptures visibles dans le flux
- Pour que ces tuiles aient l’air déjà actives plutôt que vides, l’équipe a choisi d’enregistrer des instantanés des tuiles ayant atteint un état stable, puis de les charger juste avant leur entrée à l’écran
- Dans la bande dessinée finale, seules les tuiles rendues existent réellement dans la simulation physique
- Pour donner l’impression qu’il y a davantage de machine au-dessus de l’écran, les tuiles de la rangée supérieure de la simulation génèrent et alimentent des billes au rythme attendu par les contraintes d’entrée
-
Instantanés au moment de l’approbation
- La génération d’instantanés est liée à l’UI de modération
- Les modérateurs doivent attendre au moins 30 secondes avant d’approuver une tuile, et l’état au moment où ils cliquent sur le bouton d’approbation est enregistré comme instantané
- Les modérateurs ont aussi la latitude d’attendre un peu plus longtemps jusqu’à ce que le dispositif soit dans un état agréable à regarder
- Le système d’instantanés a pour effet de réinitialiser les erreurs accumulées : lorsqu’un utilisateur fait défiler la page et voit une nouvelle tuile pour la première fois, il voit un état propre jugé satisfaisant par le modérateur
- Si l’on observe longtemps, de nombreux dispositifs peuvent s’arrêter ou se casser, mais en continuant à explorer, on rencontre de nouveaux instantanés
- L’ensemble de la machine n’est jamais entièrement simulé ; le résultat est une structure proche de l’hyperréalité
Structure de rendu React, DOM et Rapier
- Machine est construit sur le moteur physique Rapier
- Rapier a été choisi pour sa documentation, son API, ses éléments de base utiles et ses performances dans le navigateur via WASM grâce à son implémentation en Rust
- Au départ, l’équipe s’intéressait aussi aux garanties de déterminisme de Rapier, mais au final aucune simulation côté serveur n’a été utilisée
- Au-dessus de Rapier, ils ont écrit un React context personnalisé,
<PhysicsContext>- Il crée et gère les objets physiques Rapier dans le cycle de vie des composants React
- Il devient plus facile de développer chaque objet plaçable et surface de collision sous forme de composant « widget »
- React agit comme un scene graph rapide et grossier
- Lorsqu’une tuile est démontée, les objets physiques et le DOM associés sont nettoyés
- Le hot reload via le rafraîchissement rapide facilitait l’ajustement des formes de collision
- Les hooks de physique ont été conçus pour ne pas fonctionner en dehors de
<PhysicsContext>, ce qui a servi pour les aperçus statiques dans l’UI de modération - Avec le recul, l’équipe estime qu’il aurait été préférable de créer les objets Rapier sous forme de composants plutôt que de hooks
- react-three-rapier utilise cette approche, qui correspond mieux au diffing de React
- L’approche basée sur
useEffectsupprime l’ancienne instance et en crée une nouvelle lorsque les dépendances changent
-
Rendu uniquement avec le DOM
- Machine est rendu entièrement avec le DOM
- Au départ, l’équipe pensait pouvoir basculer vers PixiJS ou canvas si les limites de performance étaient atteintes, mais elle a poussé aussi loin que possible l’approche DOM, qui demandait moins de choses à construire
- Pour les performances de rendu, la boucle de frame applique directement les styles des widgets soumis à la simulation physique
- Le diff React ne s’exécute que lorsque la structure du graphe de scène change
- Au départ, les billes étaient aussi rendues avec React, mais leurs créations et suppressions fréquentes augmentaient le coût du diff ; un renderer optimisé séparé a donc été créé
- Du draw culling est appliqué aux billes et widgets hors écran
- Cette approche a bien fonctionné avec 4 000 billes simulées et plusieurs centaines de billes visibles à l’écran, ce qui a validé le rendu uniquement avec le DOM
API, modération et gestion des soumissions
- Le backend a été écrit en Haskell par davean et Kevin, avec Redis comme stockage
- OpenAPI et OpenAPI fetch ont été utilisés pour partager les types entre bases de code
- Il y a eu quelques frictions initiales pour l’adapter aux types Haskell
- Cela a aidé à coordonner les changements d’API de dernière minute
- TanStack Query s’est révélé utile pour gérer le cache et le rafraîchissement automatique sans push serveur
-
UI de modération et priorités
- L’UI de modération conçue par Ed White était le goulot d’étranglement par lequel passaient toutes les soumissions avant publication
- Les modérateurs pouvaient devoir choisir parmi des centaines de designs candidats pour une tuile donnée
- La priorité de la file était déterminée par un score d’intérêt attribué à chaque type de widget, puis par le comptage de chaque instance afin de trier les tuiles candidates
- Cette méthode favorise les solutions contenant beaucoup d’éléments, mais les modérateurs examinaient le milieu de la liste pour compléter avec des solutions plus minimalistes
- Le grand déséquilibre entre le nombre de designs soumis et le nombre de designs effectivement publiés dans la machine reste un regret
- Avant le lancement, l’équipe a cherché des moyens de publier davantage de backlog, mais n’a pas trouvé de bon compromis dans les contraintes de temps de modération
- Elle souhaite trouver une manière de partager davantage le jeu de données des soumissions une fois la période de soumission en direct terminée
-
Cooldown d’approbation et réglage de la vitesse
- Comme la qualité des instantanés de tuiles était importante, le bouton d’approbation des modérateurs restait désactivé tant que la simulation n’avait pas tourné au moins 30 secondes
- Ce cooldown permettait de créer un instantané d’état stable et de vérifier que les sorties recevaient les billes au rythme attendu
- L’équipe pensait au départ que cela serait pénible pour les modérateurs, mais ceux-ci l’ont bien accueilli, car il empêchait les décisions précipitées
- Après le lancement, un curseur a été ajouté pour permettre aux modérateurs de faire tourner la simulation bien plus vite qu’en temps réel
- Cette fonctionnalité a permis de voir les 30 premières secondes d’une soumission en moins de 5 secondes, tout en facilitant l’examen de comportements sur une durée plus longue
Interactions de tuiles non intentionnelles
- « Jamslunt Interfoggle » est un dispositif publié dans les premières heures après la mise en ligne, dont le mécanisme exploite la portée étroite d’un ventilateur
- Ce dispositif accumule des billes bleues dans un passage puis, lorsque leur poids devient suffisant, les déverse des deux côtés
- Placé au-dessus, « Bouncy » est un moteur chaotique qui lance des billes dans un croisement à trois branches
- Bouncy envoie parfois des billes vertes vers la mauvaise sortie ; celles-ci brisent l’amas stagnant de billes bleues et créent un flux en chaîne dans Interfoggle
- Dans l’éditeur, pour rendre les entrées faciles à comprendre, seules les bonnes couleurs étaient fournies ; Interfoggle n’a donc pas pu être conçu en tenant compte de ce comportement des billes vertes
- Ces combinaisons non intentionnelles, et la façon dont les gens utilisent créativement les outils sur un canevas partagé, sont devenues l’un des grands plaisirs du projet
Code et expérimentations restantes
- Le code source de Machine est disponible dans le dépôt GitHub
- Une implémentation simulant entièrement la machine de manière globale reste un sujet de hack intéressant
- Le lien permettant d’ajouter directement un design à Machine se trouve dans xkcd 2916
1 commentaires
Commentaires sur Hacker News
Ce qui m’a fait rire en lisant ce billet, c’est qu’à l’époque je n’avais absolument pas compris que quelque chose comme ça était en train de se passer.
Il ne semblait pas y avoir d’explication sur la situation, je ne savais pas non plus que c’était une expérience collective, et j’avais juste l’impression que plein de choses aléatoires se produisaient de façon chaotique.
J’ai terminé et soumis quelques tuiles, pensant que c’était la manière de passer à « l’étape suivante », et je leur ai donné des noms idiots comme « test 1b ». Je pensais que c’était en solo, donc que j’étais le seul à voir les noms.
Après en avoir fait quelques-unes, je me suis lassé ; en me baladant, j’ai vu des choses complexes, mais je n’ai pas compris que c’étaient des soumissions, je pensais que c’étaient juste des points de départ pour résoudre des niveaux. Au final, je me suis bien fait avoir par le poisson d’avril.
Peut-être parce que je n’avais jamais joué au jeu de machines original qui l’a inspiré :-)
Je crois que j’ai tué rapier en ajoutant plein d’éléments « bonk ».
Uncaught Error: recursive use of an object detected which would lead to unsafe aliasing in rustat jt (rapier_wasm2d_bg.js:4836:11)at 4ea5626ea4b1e4145572.module.wasm:0xf061cat 4ea5626ea4b1e4145572.module.wasm:0xf0638at 4ea5626ea4b1e4145572.module.wasm:0xb5e7bat H.remove (rapier_wasm2d_bg.js:1051:14)at l.remove (collider_set.js:87:18)at y.removeCollider (world.js:343:28)at PhysicsContext.tsx:258:15Cela dit, c’est vraiment amusant, et je regrette de ne pas avoir su que c’était ouvert en temps réel. Ce serait vraiment bien de pouvoir créer des liens permanents vers les machines individuelles créées par les gens.
Je comprends qu’il puisse y avoir un problème de stockage, mais pourquoi ne pas encoder le JSON en base64 et le mettre dans un paramètre d’URL ? J’aimerais créer des cartes bizarres et les partager avec d’autres.
Les machines incluses dans la version publique complète peuvent avoir des liens permanents, mais pas les créations individuelles qui n’ont pas été sélectionnées dans la file de modération.
C’était un choix délibéré pour éviter les risques liés à l’hébergement de contenu généré par les utilisateurs non modéré sur le domaine de la BD.
Pour info, le sujet avait aussi été publié sur HN le 6 avril, avec 14 commentaires.
https://news.ycombinator.com/item?id=39953514
« Il n’y avait pas d’incitation à réfléchir soigneusement à l’endroit où placer les autocollants. Les joueurs n’avaient pas assez de capacité d’action, par leurs actions individuelles, pour faire avancer l’intrigue. La créativité s’est donc limitée à de simples motifs, comme répéter des autocollants similaires en tuiles ou tracer des lignes. »
Ah, le jeu est devenu la vie en entreprise dans une grande boîte.
J’y ai participé quand c’était public. Je crois avoir passé environ une heure à essayer de rendre mon système aussi fiable que possible pour que la bonne bille arrive à la bonne sortie.
Après l’avoir soumis, j’ai rafraîchi la page et il y avait l’appareil de quelqu’un d’autre à sa place. Il faut reconnaître qu’il était plus joli, mais sa fiabilité était moindre.
J’aurais aimé que ce fonctionnement soit expliqué un peu plus tôt. Et je ne pense pas être le seul à ne pas avoir compris que la liste des blocs de construction était défilable.
Maintenant, je n’ai plus le courage d’y retourner pour vérifier :(
Waouh, un ami et moi avions eu la même idée en 2014 et l’avions implémentée pour Ludum Dare. https://nickfa.ro/wiki/CoinSlot
C’est chouette de voir cette idée ressortir sous une forme plus aboutie et qui fonctionne bien.
Ça me rappelle ma jeunesse. J’y ai joyeusement gaspillé un nombre incroyable d’heures.
https://www.myabandonware.com/game/the-incredible-machine-1m...
J’ai peut-être raté quelque chose, mais pourquoi certains éléments semblent-ils n’agir que sur des billes de certaines couleurs dans la machine ?
J’imagine que c’est un mécanisme pour empêcher les couleurs de se mélanger complètement, mais je ne crois pas que ce soit expliqué dans le billet.
La bille jaune est légère et a une forte résistance à l’air, la verte est lourde, et la rouge rebondit énormément.
Cela permet donc de concevoir des trieurs physiques.
J’aimerais qu’il y ait un moyen simple de vérifier si l’une des machines que j’ai créées a été intégrée à la version finale.
Dans une prochaine conception, ce serait pas mal de stocker les titres des soumissions précédentes quelque part, comme dans le stockage local, et d’afficher une notification.