Cuando la demo falló en silencio
Una caída te dice que algo está mal. Un fallo silencioso les entrega a tus usuarios, y a tus socios, una cáscara pulida y vacía. El motor que gana confianza es el que se niega a susurrar.
Hay una clase de bug que es peor que una caída. Una caída al menos te dice que algo está mal. El bug con el que me senté este sábado por la mañana era la otra cosa. La demo arrancaba. La escena cargaba. La interfaz renderizaba. El menú de pausa no respondía. La jugabilidad era un relleno. Sin error. Sin excepción. Ninguna línea de registro por encima de INFO. El motor había fallado en silencio y educadamente estaba simulando los gestos de un juego que funciona.
Esta es la entrada sobre cómo hacer que tu motor se niegue a hacer eso.
Cómo se veía la demo para un usuario
Arrancaba limpio. Pantalla de bienvenida. Pantalla de menú. Clic en «Jugar». Transición de escena. Una escena de aspecto limpio. Una nave en el medio. Un indicador de puntaje en la esquina superior derecha.
La nave no se movía. La entrada estaba muerta. El menú de pausa, al invocarse, estaba presente visualmente pero no respondía a los clics. El puntaje decía cero y se quedaba en cero. No había enemigo al cual dispararle. El mundo era una cáscara educada, vacía, de apariencia funcional.
Para un desarrollador que lea esto, la conclusión es obvia en treinta segundos: esto es un relleno. Faltan los prefabs reales de jugabilidad. El motor cayó a un modo degradado y se olvidó de decírselo a alguien.
Para un usuario, este es el peor tipo de fallo. La aplicación no se ve rota. Se ve terminada, y mala.
Qué estaba realmente mal
Dos problemas distintos, cada uno sutil.
El cargador de escenas se estaba degradando en silencio. El SceneContentLoader es responsable de encontrar los prefabs de jugabilidad e instanciarlos en la escena activa. Cuando faltan los prefabs (por un problema de compilación, un paquete de activos faltante, o un desajuste de configuración) cae a una configuración de relleno. Se suponía que el fallback registrara un error claro. Estaba registrando a nivel INFO. El mensaje de error estaba enterrado en un flujo de registro sin filtrar junto a otras trescientas líneas de INFO. Cualquiera que ejecutara la demo y hojeara el registro no vio nada alarmante.
Al lienzo del menú de pausa le faltaba un GraphicRaycaster. Esto es algo del lado de Unity. Un lienzo que no tiene un componente GraphicRaycaster no puede recibir eventos de puntero. El menú de pausa se estaba instanciando correctamente, renderizando correctamente, y estaba completamente sordo a la entrada del usuario. La ausencia del componente se había colado a través de una refactorización que consolidó varias configuraciones de lienzo en una sola. La consolidación eliminó el raycaster de uno de los lienzos.
Ambos bugs tenían el mismo sabor. Algo salió mal en silencio. La ruta de código continuó. El usuario vio algo que parecía funcionar pero no funcionaba.
Cómo los encontré
La auditoría tomó más tiempo que el arreglo. El arreglo tomó una tarde de sábado. La auditoría tomó la mañana. El patrón que quiero dejar escrito porque se va a repetir.
La primera pista fue que el puntaje estaba atascado en cero. Asumí que el bug del fin de semana pasado (puntaje contando doble) se había sobrecorregido. Equivocado. El puntaje era cero porque no había enemigos. No había enemigos porque los prefabs de jugabilidad no estaban cargando. Los prefabs no estaban cargando porque el cargador de contenido de escena estaba cayendo a un relleno y diciéndolo a nivel INFO en lugar de nivel ERROR.
Una vez que supe eso, el segundo bug se volvió obvio. Que el menú de pausa no respondiera era un problema separado en la misma demo, que salió a la superficie en la misma sesión de sábado. El patrón de auditoría es «si encontraste un fallo silencioso, busca otros adyacentes».
Qué arreglé
Un conjunto pequeño pero preciso de cambios.
Promover la advertencia de relleno a ERROR. Cuando el cargador de contenido de escena no puede encontrar un prefab real de jugabilidad y cae a modo relleno, ahora registra a nivel ERROR con el mensaje «PLACEHOLDER MODE: gameplay prefabs missing, demo will not function correctly». La severidad ERROR hace que sobreviva cualquier filtrado razonable de registros. El texto exacto «PLACEHOLDER MODE» es inconfundible cuando haces grep en el registro.
Agregar un paso EnsureCrossPlatformInputManager. Incluso en modo de relleno, el sistema de entrada debería funcionar. Si un desarrollador está probando la demo en modo de relleno (porque está trabajando en la interfaz sin el paquete completo de activos), necesita poder interactuar con los menús. El arranque ahora asegura que exista un singleton CrossPlatformInputManager en cualquier escena, incluso las de relleno.
Agregar GraphicRaycaster automáticamente a los lienzos que lo necesiten. El arreglo defensivo es hacer que el helper de creación de lienzos verifique el componente GraphicRaycaster y lo agregue si falta. Esto no va a tapar futuros bugs de la misma forma, pero elimina la posibilidad de este modo de fallo específico.
Registro de arranque detallado en AutoBootstrap. Cuando la demo arranca, los registros ahora imprimen un resumen breve de qué escena se cargó, en qué modo cargó (real vs. relleno), y qué subsistemas están presentes. Un desarrollador leyendo el registro puede responder la pregunta «¿la demo arrancó en el modo que esperaba?» en diez segundos. Antes de esta mañana, la respuesta requería leer trescientas líneas de registro e inferir.
Una prueba unitaria para el helper de lienzo. Porque el bug era un componente faltante, el tipo correcto de prueba es una que verifique que el componente está presente después de que el helper corra. Esa prueba ya está en la suite. Si alguien refactoriza el helper de lienzo de una forma que vuelva a eliminar el raycaster, la prueba fallará ruidosamente.
A qué se generaliza esto
Tres patrones para llevarse de esta mañana.
El fallo silencioso es el peor modo de fallo. En cualquier lugar donde tu código caiga a un modo degradado, el fallback tiene que gritar. No a nivel INFO. A nivel ERROR. Con una cadena que grep va a encontrar. Si el usuario no puede saber si el fallback se disparó, tampoco puede el desarrollador que lee los registros tres días después.
Un componente faltante en un sistema impulsado por configuración necesita una prueba. Unity, Unreal, cualquier motor donde una escena se configura a partir de componentes: la configuración puede derivar en silencio. La defensa es un pequeño conjunto de pruebas que verifiquen «la escena de tipo X tiene los componentes A, B, C». Pruebas aburridas. Pruebas críticas. Vale la pena escribirlas.
Auditar por adyacencia. Cuando encuentres un fallo silencioso, mira todos los sistemas adyacentes. Los bugs se agrupan. La misma refactorización que eliminó el raycaster podría haber eliminado otros componentes en otros lienzos. El mismo desliz de disciplina de registro que enterró la advertencia de relleno podría haber enterrado otras advertencias. Investiga el vecindario, no solo el avistamiento original.
Lo que socios y constructores deberían llevarse de esto
Si estás construyendo algo donde el motor tiene múltiples modos degradados (activo faltante, red caída, SDK de proveedor no disponible), el motor tiene que decirte en qué modo está, ruidosamente, cada vez. La degradación silenciosa es un defecto.
Si estás evaluando un motor para una asociación, pregúntale al equipo cómo manejan los fallos silenciosos. La respuesta correcta es «los hacemos salir a la superficie a nivel ERROR con cadenas rastreables con grep y tenemos auditorías para encontrarlos». La respuesta equivocada es «todavía no hemos visto ese problema».
Si estás corriendo un flujo de trabajo impulsado por agentes y tus agentes están haciendo refactorizaciones, las refactorizaciones ocasionalmente van a eliminar componentes o degradar la severidad de registro por accidente. La solución es un pequeño gate de CI que verifique que los componentes críticos están presentes y que los mensajes críticos de registro sobreviven en la severidad correcta. No glamoroso. Efectivo.
Tarde de sábado. La demo habla cuando algo está mal. El menú de pausa vuelve a responder a los clics. La fiesta con la demo educadamente vacía se terminó.
De vuelta a construir.
Un motor que te dice en qué modo está
RakuAI hace salir a la superficie cada modo degradado ruidosamente, a nivel ERROR, con cadenas rastreables con grep y auditorías que encuentran primero los fallos silenciosos. Esa es la disciplina de fiabilidad que un runtime espacial le debe a los equipos que construyen sobre él.