Simular un entorno de ejecución que dice no: cómo aislamos el navegador para que se comporte como WeChat
Por Oleg Sidorkin, CTO y cofundador de Cinevva

Hace poco lanzamos un modo de minijuegos de WeChat en nuestro Creador de Juegos: un perfil de compilación que mantiene los juegos generados por IA dentro del subconjunto de la plataforma web capaz de sobrevivir a una adaptación al entorno de ejecución de WeChat. El perfil es un conjunto de reglas de generación. Nada de interfaces DOM, Pointer Lock, audio que no sea MP3 ni código dinámico. El modelo las sigue como los modelos suelen seguir las reglas; es decir, casi siempre.
«Casi siempre» no basta cuando todas las infracciones son invisibles en el navegador y fatales tras exportar. Un juego con una barra de salud en HTML funciona perfectamente en nuestra vista previa y muestra una pantalla en blanco en WeChat, porque los minijuegos de WeChat no tienen DOM. Así que construimos lo que convierte esas reglas de sugerencias en leyes físicas: un sandbox de modo estricto que hace que el propio navegador se niegue a hacer lo que WeChat no puede hacer. Este artículo explica cómo funciona.
La decisión fundamental: simular la ausencia, no la presencia
El enfoque obvio sería emular WeChat: implementar wx.createCanvas, wx.onTouchStart, wx.setStorageSync y ejecutar los juegos contra la superficie emulada de wx.*. Nosotros tomamos el camino contrario, y la razón está en la estructura de nuestro proceso de exportación.
Los juegos del modo WeChat siguen escribiéndose contra las API del navegador. Durante el empaquetado, un adaptador —siguiendo el enfoque estándar de la comunidad de weapp-adapter— asigna esas API del navegador a wx.*. Eso significa que nuestros juegos nunca llaman directamente a wx.*, por lo que un emulador de wx probaría rutas de código que no existen. Lo que realmente rompe una adaptación no es la ausencia de wx.* en el navegador. Es la presencia de API del navegador sin equivalente en WeChat, utilizadas inadvertidamente durante la generación. document.createElement('div'). requestPointerLock(). IndexedDB. Un archivo .ogg que Chrome decodifica sin problemas y que nunca funcionará en WeChat para iOS.
Por eso el sandbox simula la ausencia. Todo lo que WeChat no tiene se elimina, se bloquea o se señala en la vista previa, y el juego se desarrolla desde su primer fotograma dentro de la intersección entre ambas plataformas.
Dónde reside: en un service worker que ya existía
La vista previa de nuestro editor sigue una ruta de servicio poco habitual que hizo que esto resultara casi gratuito. Los juegos del editor no se sirven desde la red. Un service worker intercepta las solicitudes a /game/* y sirve los archivos desde IndexedDB; así, el iframe de vista previa se recarga al instante sin un viaje de ida y vuelta al servidor. Ese worker ya inyectaba dos archivos virtuales en cada página del juego: un script de captura de errores que reenvía al editor la salida de la consola y los fallos, y un inspector de escenas en vivo.
El sandbox es el tercer archivo virtual, _wechat-strict.js, que se inyecta en el <head> de la página inmediatamente después del script de captura y antes de ejecutar cualquier código del juego. El orden es crítico por partida doble. Debe ejecutarse después de la captura para que sus llamadas a console.error ya estén interceptadas y lleguen al editor, y antes de los módulos del juego para que los bloqueos estén preparados cuando se ejecute la primera línea de código.
La activación usa un truco de una sola línea. El interruptor de WeChat del creador se conserva en localStorage, y el iframe de vista previa comparte origen con el editor, así que el entorno de pruebas se limita a leer directamente la opción:
try {
if (localStorage.getItem('cinevva-gc-profile') !== 'wechat-minigame') return;
} catch (e) { return; }Para cualquier juego que no sea de WeChat, el script consiste en una única comparación de cadenas y un retorno anticipado. Sin indicador de compilación, sin viajes de ida y vuelta al servidor y sin estado que sincronizar con el service worker.
La lista de bloqueos y la taxonomía de dos niveles
No todas las infracciones merecen la misma respuesta, así que el entorno de pruebas distingue dos niveles. Las API que no existen en WeChat lanzan una excepción, porque eso es lo que sucede después de exportar y un fallo durante la generación es la representación más honesta de un fallo durante la revisión. Las cosas que existen pero fallan más adelante se señalan mediante errores claros en la consola sin modificar su comportamiento, porque bloquearlas ocultaría el estado real del juego a la persona que está iterando sobre él.
El nivel que lanza excepciones: la creación de cualquier elemento de interfaz HTML, tanto mediante createElement como mediante createElementNS —Three.js crea su canvas con la variante NS, así que ambas rutas necesitan el mismo control—, eval, new Function, requestPointerLock y el registro de service workers. Cada excepción incluye la solución en su mensaje:
throw violate("document.createElement('" + tag + "') — Los minijuegos de WeChat no tienen DOM. " +
"Dibuja la interfaz en un canvas (canvas 2D fuera de pantalla -> THREE.CanvasTexture sobre un quad en espacio de pantalla) " +
"y realiza tú mismo la detección de toques.");El nivel de avisos: leer indexedDB —devuelve undefined, exactamente igual que WeChat, además de un error que señala a localStorage—, audio en cualquier formato que WeChat para iOS no pueda decodificar —comprobado en tres lugares: el constructor Audio, el setter src del prototipo del elemento multimedia y las URL obtenidas—, solicitudes de red a cualquier origen que no sea el del propio juego o nuestra CDN de recursos —en WeChat requieren un dominio incluido en la lista blanca y registrado mediante ICP— y la aparición de Tone.js en cualquier lugar.
También hay una auditoría posterior a la carga que se ejecuta un momento después de que la página se estabilice: recorre <body> e informa de cualquier elemento HTML introducido mediante el propio marcado del juego en lugar de crearse dinámicamente. Los HUD estáticos eluden fácilmente un control sobre createElement, así que ese control por sí solo no basta.
Todos los informes se deduplican por mensaje y se limitan a cincuenta por sesión. Un error de bucle invertido que crea un div por fotograma produce un solo error, no una avalancha que ahogue la señal.
Las cuatro excepciones que más nos enseñaron
Construir un sandbox consiste principalmente en decidir qué no bloquear, y cada excepción surgió cuando nuestras propias herramientas dejaron de funcionar durante el desarrollo.
Nuestro depurador funciona con eval. La herramienta execute_js del creador, que la IA utiliza para inspeccionar el juego en ejecución, evalúa código mediante el gestor de mensajes del script de captura, que llama a eval. Bloquear eval sin más habría cegado a la propia IA. La solución: antes de bloquearlo, el entorno guarda la función real en una propiedad no enumerable, y el gestor del script de captura recurre a ella:
Object.defineProperty(window, '__cinevvaRealEval', { value: window.eval, enumerable: false });
window.eval = function () { throw violate('eval() está prohibido en los minijuegos de WeChat.'); };
// script de captura, al recibir el mensaje:
returnValue = (window.__cinevvaRealEval || eval)(e.data.code);El código del juego que intente utilizar eval sigue fallando. El depurador no.
El grabador también crea elementos. Nuestro grabador de vídeos cortos se inyecta en el iframe del juego como un <script> creado mediante el propio document.createElement del iframe, y descarga los vídeos terminados mediante un clic sintético en un elemento <a>. Bloquear esas etiquetas habría roto la grabación únicamente en el modo WeChat. Por eso script sigue estando permitido y a genera un aviso sin lanzar una excepción. Un juego que incluya un enlace real sigue siendo señalado, y las herramientas continúan funcionando.
Los eventos de teclado se mantienen. La IA verifica los controles mediante una herramienta que simula pulsaciones de teclas y después comprueba cómo ha cambiado el estado del juego. Suprimir la entrada de teclado para simular un teléfono habría destruido el ciclo de verificación de controles que detecta errores de cámara invertida. La prioridad de los controles táctiles se aplica mediante las reglas de generación y el esquema de controles del perfil, no mediante el sandbox.
WebGL2 se mantiene, y el motivo merece su propia sección más abajo. La lista inicial de bloqueos impedía usar getContext('webgl2') para forzar el renderizado «compatible con WebGL1» que exigía el perfil. El problema es que Three.js eliminó por completo la compatibilidad con WebGL1 en r163, y nosotros fijamos la versión r181: bloquear WebGL2 habría dejado en blanco todos los juegos 3D del modo. La regla de renderizado del perfil era incoherente y se había escrito desde una concepción desactualizada de la plataforma. La auditoría del sandbox terminó auditando la propia especificación del sandbox, y seguir ese hilo nos llevó a un hallazgo importante.
La cuestión de WebGL2, respondida como es debido
No interferir con la creación del contexto planteó la siguiente pregunta obvia: si nuestro motor requiere WebGL2, ¿dejan de funcionar los juegos en dispositivos WeChat que solo tienen WebGL1? Investigarla nos dio la imagen más clara que tenemos del mínimo de renderizado de la plataforma, así que aquí está, acompañada de fuentes.
En Android, el entorno de ejecución de minijuegos de WeChat ofrece WebGL2 con cualquier biblioteca base moderna, y el hardware GLES3 subyacente es prácticamente universal. iOS es donde está la verdadera historia. La documentación técnica de WeChat sobre la compatibilidad con WebGL2 y el modo high-performance+ explica que, en iPhone, WebGL2 solo está disponible correctamente en el entorno high-performance+, que requiere un cliente reciente de WeChat —8.0.45 o posterior, y una versión superior en iOS 14— y, en la práctica, iOS 15.5 o posterior. Según la propia descripción de Tencent, el modo high-performance normal ejecuta WebGL2 «con más problemas», y el entorno normal de iOS no lo ofrece en absoluto.
La forma del fallo es lo realmente peligroso. En entornos incompatibles, getContext('webgl2') puede devolver un contexto válido en apariencia pero roto en lugar de null, un comportamiento que los desarrolladores han pedido directamente a Tencent que corrija. Un juego que confíe en el valor devuelto no falla de forma limpia. Muestra gráficos corruptos o una pantalla negra sin ningún aviso.
Así lo abordamos, en tres capas. El sandbox no modifica la creación de contextos WebGL2, porque bloquearla supondría luchar contra nuestro propio motor. Ahora, el perfil de generación exige una comprobación de arranque en todos los juegos: la creación del renderizador debe estar envuelta en try/catch y seguida de una prueba funcional (typeof gl.createVertexArray === 'function', una capacidad exclusiva de WebGL2 que un contexto falso no tendrá), con un mensaje cordial dibujado en el canvas que diga «actualiza WeChat» si falla, en lugar de mostrar una pantalla en blanco. Es el mismo patrón que emplean los minijuegos convertidos desde Unity, razón por la cual normalmente se ven avisos de actualización y no pantallas negras. Y, cuando esté listo, el exportador fijará el modo high-performance+ en game.json para que la ruta compatible sea la predeterminada.
¿Cuál es el coste de exigir WebGL2 para la audiencia? Los usuarios con versiones anteriores a iOS 15.5 o clientes de WeChat anteriores a la 8.0.45: un porcentaje bajo de un solo dígito en 2026, pero concentrado en dispositivos antiguos, lo que importa más en algunos géneros que en otros. Si los datos reales de distribución llegan a indicar que merece la pena perseguir esa cola, tenemos reservada una vía de escape barata: fijar el perfil de WeChat en Three r162, la última versión compatible con WebGL1. Los juegos del perfil solo utilizan API básicas y estables, por lo que bajar de versión supone cambiar una sola línea del mapa de importaciones; una opción que mantendremos deliberadamente sin usar hasta que los datos la justifiquen.
Cerrar el ciclo con el modelo
Esta es la parte que hace que todo esto merezca la pena para un producto centrado en la IA. Después de cada compilación, el agente del creador llama a una herramienta que lee la consola del juego y espera a que termine el arranque. El script de captura reenvía console.error a ese flujo. Por eso, una infracción [wechat-strict] no es un aviso que una persona pueda pasar por alto al desplazarse. Llega al canal exacto que el modelo ya comprueba antes de declarar terminada una compilación, en el mismo turno y con la solución explicada en el mensaje.
El perfil de generación cierra la última brecha con una instrucción: tratar cada mensaje [wechat-strict] como un error que impide completar la compilación, corregir la causa y no intentar nunca detectar ni eludir el entorno de pruebas. Un juego que solo funciona con el sandbox desactivado es un juego que fallará después de exportarlo, y eso es exactamente lo que se le dice al modelo.
En la práctica, el sandbox convierte un problema difuso de cumplimiento —«¿ha seguido el modelo las catorce reglas?»— en el ciclo de depuración que el sistema ya sabe resolver bien —«la consola muestra un error; corrígelo»—.
Lo que un navegador no puede simular
La sección de honestidad. Este sandbox detecta infracciones relacionadas con la superficie de las API, que, según nuestras estimaciones, representan la gran mayoría de los problemas capaces de arruinar una adaptación. No puede detectar el rendimiento en un teléfono real, la ejecución de JavaScript sin JIT en iOS, las peculiaridades de la implementación WebGL de WeChat ni las diferencias de comportamiento de los códecs más allá de las extensiones de archivo. Para eso hace falta el entorno real.
Por eso el sandbox es el primero de tres niveles. El segundo, una vez que nuestro exportador genere proyectos para las herramientas de desarrollo de WeChat, será el simulador oficial controlado sin interfaz mediante la CLI de DevTools y miniprogram-automator: arrancar el paquete exportado, comprobar que se renderiza el primer fotograma y automatizar un toque. El tercero es la ruta oficial en dispositivos: códigos QR de vista previa y depuración remota en teléfonos físicos, donde residen las verdades que ningún otro entorno puede revelar. Cada nivel es más lento y más fiel que el anterior, y la función de cada uno es hacer que resulte poco frecuente tener que pasar al siguiente.
Si quieres probar el modo, activa el interruptor «Modo de minijuegos de WeChat» en el Creador de Juegos. Para conocer el contexto comercial y normativo, consulta nuestra guía práctica para llegar a los quinientos millones de jugadores de WeChat.
Referencias
- Documentación oficial de WeChat: compatibilidad con JavaScript en minijuegos
- Documentación oficial de WeChat: la capa de adaptación
- Documentación oficial de WeChat: modo de alto rendimiento+
- Documentación de ingeniería de WeChat: compatibilidad con renderizado WebGL2 en minijuegos
- Documentación de ingeniería de WeChat: modos de alto rendimiento y alto rendimiento+ en iOS
- Comunidad de desarrolladores de WeChat: los contextos WebGL2 no funcionales deben devolver null
- Documentación oficial de WeChat: miniprogram-automator
- Notas de la versión r163 de Three.js (se eliminó la compatibilidad con WebGL1)