- Machine de xkcd es un enorme juego de máquina de bolitas al estilo Rube Goldberg construido al unir dispositivos en mosaico creados por los lectores, y la idea se convirtió en un cómic interactivo real en solo 3 semanas
- La experiencia previa con proyectos participativos llevó al criterio de diseño de que, para que un lienzo compartido funcione bien, se necesita un contexto y propósito compartidos
- Para preservar la expresividad de los jugadores pero mantener la compatibilidad entre mosaicos, se impusieron fuertes restricciones de entrada y salida, y se limitó cada dispositivo para que alcanzara un estado estable en menos de 30 segundos
- En lugar de simular toda la máquina en tiempo real, solo se ejecuta con Rapier la zona visible, y con una instantánea tomada en el momento de la aprobación se hace que parezca un dispositivo ya en funcionamiento
- El flujo de aprobación y publicación de envíos se operó combinando renderizado con React y DOM, backend en Haskell, Redis, OpenAPI, TanStack Query y una UI de moderación
El punto de partida de Machine
- xkcd lanzó Machine el 5 de abril
- Machine es un constructor gigante de Rube Goldberg machine al estilo del juego clásico The Incredible Machine
- La máquina completa está formada por pequeños mosaicos de dispositivos creados por distintos lectores de xkcd
- El equipo hizo Machine en 3 semanas, y la idea surgió del GIF colaborativo de 2005 Blue Ball Machine
- Las preguntas clave de la lluvia de ideas inicial fueron de dónde venían las bolitas, si la máquina que todos veían era la misma, cuál era el propósito de la máquina, cómo interactuarían los jugadores y qué los motivaría a participar
Lecciones aprendidas de los xkcd participativos
- Entre los cómics interactivos previos de xkcd centrados en contenido creado por usuarios, Lorenz permitía que los lectores escribieran el texto de los paneles para desarrollar chistes e historias, y quedó como una buena experiencia
- En 2020, Collector’s Edition hacía que los jugadores buscaran stickers en el archivo de xkcd y los pegaran una vez en un lienzo compartido global, pero no funcionó tan bien como se esperaba
- Todos los jugadores empezaban en el centro de un mapa vacío, y pronto una pantalla caótica se convertía en la primera impresión
- Había poco incentivo para elegir con cuidado la ubicación de los stickers, y era difícil hacer avanzar una narrativa solo con acciones individuales
- Como no había una historia ni un objetivo compartidos, no quedaba claro cómo se conectaba cada sticker con los demás elementos de la página
- Para que un lienzo colectivo funcione bien, los usuarios deben poder aprender con ejemplos qué tipo de cosas sería genial crear
- Para orientar los resultados creativos en una sola dirección, hace falta un contexto y propósito compartidos que sincronice qué se está intentando construir
Diseño de restricciones: expresividad, compatibilidad y estado estable en 30 segundos
- Incluso después de decidir construir un gran dispositivo colaborativo de caída de bolitas, seguían pendientes problemas como el tamaño total de la máquina, cómo simularla y cómo integrar los mosaicos
- Si se asumía una máquina de tamaño 100x100, se consideró arriesgado intentar ejecutar en tiempo real en el cliente 10,000 mosaicos y procesar decenas de bolitas en cada uno
-
Priorizar la expresividad por encima de la exactitud
- Se evaluaron opciones como ejecutar toda la máquina en el servidor o simular mosaicos individuales para validarlos
- Como en el editor prototipo era fácil crear patrones caóticos de colisión entre bolitas, se llegó a la conclusión de que exigir una máquina predecible reduciría la libertad de los jugadores
- El diseño final priorizó la flexibilidad del jugador, incluso permitiendo dispositivos muy no deterministas o defectuosos
- Esta decisión hizo necesaria una moderación activa para verificar que los mosaicos cumplieran las restricciones y eliminar contenido ofensivo
-
Restricciones de entrada y salida para compatibilidad entre mosaicos
- Al principio se consideró dejar que el siguiente jugador extendiera libremente el diseño según la posición de salida del mosaico anterior
- Pero si más adelante había que reemplazar un mosaico colocado al principio, podía romperse una gran zona que dependiera de él
- Por eso se impusieron fuertes restricciones de entrada y salida para que varios jugadores pudieran crear diseños compatibles dentro del mismo espacio de mosaico
- Este enfoque conecta con el Robustness principle: “sé conservador en lo que envías y liberal en lo que aceptas”
- El map generator de Kevin comienza con rompecabezas simples de 1 entrada y 1 salida, se vuelve más complejo con fusiones intermedias de 4 entradas y 4 salidas, y al final vuelve a 2 salidas por mosaico
- El editor ofrece retroalimentación en tiempo real mientras los jugadores crean un mosaico
- En promedio, un mosaico debe enviar bolitas a una velocidad similar a la que las recibe
- Se busca reducir dispositivos que se traguen bolitas o generen grandes demoras
- Para reflejar variaciones en la entrada aguas arriba, se aplicó chaos testing aleatorizando la velocidad de las bolitas que entran al editor
-
Debe alcanzar un estado estable en menos de 30 segundos
- Para reducir cuánto tiempo debía observar un moderador, se estableció el criterio arbitrario de que un dispositivo debía entrar en un estado estable en un plazo de 30 segundos
- La referencia fue el cálculo de que, si se vieran 10,000 mosaicos durante 30 segundos cada uno, el tiempo total de moderación sería de unas 83.3 horas
- También se hizo que las bolitas expiraran después de 30 segundos
- Sin expiración, la primera experiencia de un jugador novato era ver bolitas acumulándose en la pantalla
- El número de rigid bodies activos aumentaba y también se volvía más lenta la simulación física
- La expiración de las bolitas evita que los errores se acumulen con el tiempo y simplifica la moderación al permitir ver, con solo 30 segundos de observación, dónde terminan la mayoría de las bolitas
Una forma de no ejecutar toda la máquina en tiempo real
- La primera gran premisa de la arquitectura de Machine fue que, si se respetaban esas restricciones, se podían unir distintos mosaicos y hacer que parecieran una sola máquina completa
- Esa premisa se verificó generando y resolviendo algunos mapas pequeños
- Como no era posible ejecutar toda la máquina en tiempo real ni en el servidor ni en el cliente, hacía falta un enfoque que solo simulara el área alrededor de lo que el usuario estaba viendo
- El objetivo era permitir seguir una sola bolita desde la parte superior hasta la inferior de la máquina
-
Un mundo físico donde solo existe el área visible
- El visor inicial del mapa solo simulaba el área visible, pero al desplazarse, los nuevos mosaicos entraban vacíos y se notaban huecos en el flujo
- Para que no parecieran mosaicos vacíos sino ya activos, se eligió guardar instantáneas de los mosaicos que hubieran alcanzado un estado estable y cargarlas justo antes de que entraran en pantalla
- En el cómic final, lo único que realmente existe en la simulación física son los mosaicos que se están renderizando
- Para que parezca que hay más máquina por encima de la pantalla, en la fila superior de la simulación se generan bolitas en mosaicos ajustadas a la velocidad esperada por las restricciones de entrada
-
Instantáneas en el momento de aprobación
- La generación de instantáneas está conectada con la UI de moderación
- El moderador debe esperar al menos 30 segundos antes de aprobar un mosaico, y el estado en el momento de presionar el botón de aprobación se guarda como instantánea
- El moderador también tiene margen para esperar un poco más hasta que el dispositivo llegue a un estado visualmente atractivo
- El enfoque de instantáneas también reinicia los errores acumulados, así que cuando el usuario ve un mosaico nuevo por primera vez al hacer scroll, lo que ve es un estado limpio que el moderador consideró bueno
- Si se observa durante mucho tiempo, muchos dispositivos pueden detenerse o romperse, pero al seguir explorando se encuentran nuevas instantáneas
- La máquina completa no se simula por completo y, como resultado, tiene una estructura cercana a la hiperrealidad
Estructura de renderizado con React, DOM y Rapier
- Machine está construida sobre el motor de física Rapier
- Las ventajas de Rapier fueron su documentación, su API, sus componentes básicos útiles y el rendimiento en navegador mediante WASM gracias a su implementación en Rust
- Al principio también interesaba la garantía de determinismo de Rapier, pero al final no se hizo simulación del lado del servidor
- Encima de Rapier se escribió un React context personalizado,
<PhysicsContext>- Crea y administra objetos físicos de Rapier dentro del ciclo de vida de componentes de React
- Facilita desarrollar cada objeto colocable y cada superficie de colisión como componentes “widget”
- React funciona como una especie de scene graph rápida y aproximada
- Cuando un mosaico se desmonta, se limpian los objetos físicos y el DOM asociados
- La recarga en caliente mediante refresh rápido facilitó ajustar las formas de colisión
- Los hooks de física se hicieron de modo que no funcionaran fuera de
<PhysicsContext>, lo cual se aprovechó para la vista previa estática de la UI de moderación - Más tarde se consideró que habría sido mejor crear los objetos de Rapier como componentes en lugar de hooks
- react-three-rapier usa ese enfoque y encaja mejor con el diffing de React
- El enfoque basado en
useEffecthace que, cuando cambian las dependencias, se destruya la instancia anterior y se cree una nueva
-
Renderizado solo con DOM
- Machine se renderiza completamente con DOM
- Al principio se pensó que, si aparecían límites de rendimiento, se podría migrar a PixiJS o a canvas, pero se llevó al máximo el enfoque con DOM para reducir lo que había que construir
- Para el rendimiento de renderizado, el frame loop aplica directamente estilos a los widgets sujetos a la simulación física
- El diff de React solo se ejecuta cuando cambia la estructura del scene graph
- Al principio las bolitas también se renderizaban con React, pero las creaciones y eliminaciones frecuentes aumentaban el costo del diff, así que se hizo un optimized renderer aparte
- Se aplicó draw culling a las bolitas y widgets fuera de pantalla
- Este enfoque funcionó bien con 4,000 bolitas en simulación y varios cientos visibles en pantalla, por lo que se mantuvo el renderizado exclusivamente con DOM
API, moderación y operación de envíos
- El backend fue escrito en Haskell por davean y Kevin, usando Redis como almacenamiento
- Para compartir tipos entre codebases se usaron OpenAPI y OpenAPI fetch
- Al principio hubo incomodidades para ajustarse a los tipos de Haskell
- Pero ayudó a coordinar cambios de API de último momento
- TanStack Query fue útil para manejar caché y auto-refresh sin push del servidor
-
UI de moderación y prioridades
- La UI de moderación diseñada por Ed White era el cuello de botella por el que debían pasar todos los envíos antes de hacerse públicos
- Puede que un moderador tenga que elegir entre cientos de diseños candidatos para un mosaico específico
- La prioridad de la cola se definía con un interestingness score por tipo de widget y contando las instancias para ordenar los mosaicos candidatos
- Este método tiende a favorecer soluciones con muchos elementos, pero los moderadores revisaban también la mitad de la lista para complementar con soluciones más minimalistas
- El gran desequilibrio entre la cantidad de diseños enviados y la cantidad realmente publicada en la máquina quedó como un punto lamentable
- Antes del lanzamiento se buscaron maneras de publicar más backlog, pero no se encontró una buena solución dentro de las restricciones de tiempo de moderación
- Después de terminar los envíos en vivo, se quiere encontrar una forma de compartir más del dataset de envíos
-
Cooldown de aprobación y control de velocidad
- Como la calidad de las instantáneas del mosaico era importante, el botón de aprobación del moderador permanecía desactivado hasta que la simulación hubiera corrido al menos 30 segundos
- Ese cooldown permitía crear una instantánea de estado estable y verificar que la salida recibiera bolitas a la velocidad esperada
- Al principio se pensó que sería molesto para los moderadores, pero se recibió bien porque evitaba decisiones apresuradas
- Después del lanzamiento se añadió un slider para que los moderadores pudieran ejecutar la simulación mucho más rápido que en tiempo real
- Con esa función, podían ver los primeros 30 segundos de un envío en menos de 5 segundos, y también resultaba más fácil revisar comportamientos de mayor duración
Interacciones no intencionales entre mosaicos
- “Jamslunt Interfoggle” fue un dispositivo subido en las primeras horas tras la publicación, con un mecanismo que usa el estrecho rango de funcionamiento de un ventilador
- Este dispositivo reúne bolitas azules en un pasaje y, cuando hay suficiente peso, hace que se derramen hacia ambos lados
- “Bouncy”, colocado encima, es un motor caótico que dispara bolitas por un cruce de tres ramales
- Bouncy a veces envía bolitas verdes a la salida equivocada, y esas bolitas rompen el montón atascado de bolitas azules, generando un flujo en cadena dentro de Interfoggle
- Como en el editor solo se suministraban los colores correctos para facilitar la comprensión de la entrada, Interfoggle no pudo haber sido diseñado pensando en ese comportamiento de las bolitas verdes
- Estas combinaciones no intencionales se convirtieron en una gran parte de la diversión del proyecto, al ver cómo la gente usaba creativamente las herramientas en un lienzo compartido
Código y experimentos pendientes
- El código fuente de Machine puede verse en el repositorio de GitHub
- Una implementación que simule completa y globalmente toda la máquina sigue siendo un reto de hacking interesante
- El enlace para añadir directamente diseños a Machine está en xkcd 2916
1 comentarios
Opiniones en Hacker News
Lo que me dio risa al leer esto es que, en ese momento, no tenía ni idea de que estaba pasando algo así.
No parecía haber ninguna explicación de qué estaba ocurriendo, no sabía que era una experiencia compartida por todos, y simplemente sentía que estaban pasando muchas cosas aleatorias de forma caótica.
Terminé algunos tiles y los envié, pensando que esa era la forma de pasar a la “siguiente etapa”, y les puse nombres tontos como “test 1b”, porque creía que era single-player y que solo yo vería los nombres.
Después de hacer algunos me aburrí, me puse a explorar y vi cosas complejas, pero no sabía que eran envíos de otras personas; pensé que eran simplemente puntos de partida para resolver niveles. Al final, supongo que caí en la broma de April Fools.
Tal vez fue porque nunca jugué el juego de máquinas original en el que se inspiró :-)
Creo que maté a rapier agregando un montón de elementos de “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:15Aun así, es realmente divertido, y me da pena no haberme enterado cuando estaba abierto en tiempo real. Sería genial poder crear permalinks también para las máquinas individuales que hizo la gente.
Entiendo que puede haber problemas de almacenamiento, pero ¿no se podría codificar el JSON en base64 y meterlo en un parámetro de URL? Quiero crear mapas raros y compartirlos con otras personas.
Las máquinas que entraron en la versión pública completa sí pueden tener permalink, pero las creaciones individuales que no fueron seleccionadas desde la cola de moderación no lo tienen.
Fue una decisión deliberada para evitar el riesgo de alojar contenido generado por usuarios sin revisar en el dominio del cómic.
Como referencia, este tema también apareció en HN el 6 de abril y tuvo 14 comentarios.
https://news.ycombinator.com/item?id=39953514
“No había ningún incentivo para pensar cuidadosamente dónde pegar los stickers. Los jugadores no tenían suficiente agencia para avanzar la trama mediante acciones individuales. Por eso la creatividad quedó limitada a patrones simples, como repetir stickers similares a modo de tiles o formar líneas.”
Ah, el juego se convirtió en trabajar en una gran corporación.
Participé cuando se publicó. Creo que pasé como una hora intentando hacer algo lo más confiable posible para que la pelota correcta llegara a la salida correcta.
Después de enviarlo, recargué la página y en ese lugar había un dispositivo de otra persona. Admito que era más bonito, pero tenía menos confiabilidad.
Me habría gustado que explicaran antes que funcionaba así. Y parece que no fui el único que no se dio cuenta de que la lista de bloques de construcción se podía desplazar.
Ya no tengo energía para volver a revisar :(
Wow, un amigo y yo tuvimos la misma idea en 2014 y la implementamos para Ludum Dare. https://nickfa.ro/wiki/CoinSlot
Es genial ver esa idea más pulida y funcionando bien.
Me hizo recordar esto de cuando era joven. Desperdicié muchísimas horas de forma muy placentera.
https://www.myabandonware.com/game/the-incredible-machine-1m...
Siento que me estoy perdiendo algo: ¿por qué ciertos elementos parecen afectar solo a pelotas de ciertos colores dentro de la máquina?
Supongo que es un mecanismo para evitar que los colores se mezclen por completo, pero no creo que el artículo lo explique.
La pelota amarilla es liviana y tiene mucha resistencia al aire, la verde es pesada, y la roja rebota muchísimo.
Así se pueden diseñar clasificadores físicos.
Ojalá hubiera una forma fácil de comprobar si alguna de las máquinas que hice llegó a la versión final.
En el próximo diseño, estaría bueno guardar los títulos de los envíos anteriores en algún lugar como local storage y mostrar una notificación.