Abre el Laboratorio de la Medusa y conocerás a nuestra mascota nadando por la oficina, con tentáculos de cuentas detrás y una mancha de luz coloreada en el suelo. Parece un sprite, pero se dibuja desde cero mientras la página está en ejecución.
La forma de la campana, la cara, los tentáculos, el grano de píxel y el brillo provienen todos de un pequeño conjunto de parámetros. Cada uno está expuesto como un control deslizante, así que el artículo sigue los controles que puedes probar tú mismo en el laboratorio.
- La campana se calcula fila por fila. Cinco formas de campana usan cinco curvas de anchura distintas; una cara solo necesita de uno a tres píxeles por ojo.
- El mundo se renderiza en un lienzo de una cuarta parte del tamaño del viewport y luego se estira de vuelta sin suavizado.
- El brillo proviene de un mapa de luz a escala 1/16 con una caída al cuadrado, niveles de luz escalonados y una matriz de tramado de 4×4.
Todas las imágenes de aquí son capturas de pantalla del laboratorio. Usamos la misma medusa color melocotón en todo momento, con el preajuste de cara dibujada a mano del autor. Nada se maquetó para el artículo.
1. El personaje es una función
Un personaje de pixel art suele empezar en un editor de imágenes. El nuestro empezó con una pregunta: ¿cuál es la descripción más pequeña de «una medusa» que aún permite que alguien haga la suya distinta de la mía?
La respuesta resultó ser una curva de anchura. La campana se dibuja fila a fila, de arriba abajo, y lo único que cambia entre una campana, un domo, una masa, una linterna y una gota es la función que dice qué ancha es cada fila. Un domo se abre rápido con un hombro suave; una masa es más ancha en el medio y estrecha en ambos extremos; una linterna se abre en el primer sexto y se recoge en el último quinto; una gota alcanza su anchura máxima a unos tres cuartos hacia abajo y luego se estrecha; una campana simple es un ensanchamiento recto.
Dos pequeñas reglas subyacen a la curva. El control deslizante de «redondez» (1–4) decide cuántas filas tarda la corona en abrirse. Además, cada fila tiene una anchura impar. Esa columna central importa más de lo que parece: los ojos y la boca pueden quedar justo en el centro en lugar de medio píxel desviados.
A partir de aquí el resto del cuerpo se sigue. Anchura, anchura superior, altura, redondez y achatamiento son cinco controles deslizantes. Como la forma se calcula en lugar de almacenarse, la campana también puede deformarse: cuando la arrastras, la fila que agarras se inclina más que las filas lejanas a tu cursor. Esa única regla es lo que hace que se sienta como goma blanda en lugar de una tarjeta rígida.
La rotación se maneja de la misma manera, pero al revés: en lugar de rotar los píxeles hacia delante (lo que dejaría huecos), el renderizador recorre el cuadro delimitador rotado, mapea cada celda de pantalla de vuelta al espacio de la campana y pregunta «¿está esto dentro de la fila r?». Vecino más cercano, sin antialiasing, así que una medusa inclinada se mantiene tan nítida como una erguida.
La cara
Los ojos se sitúan al 54% de la altura de la campana, separados un 18% de la anchura de la campana. Esas dos proporciones son todo el sistema de maquetación. La expresión es entonces solo qué píxeles de uno a tres se iluminan: un punto, una raya, un ^, o un punto desplazado un píxel hacia arriba o hacia dentro. Curiosa es un ojo ligeramente más alto que el otro. Atascada es ambos ojos empujados hacia la nariz. Somnolienta son dos rayas. Mareada —activada por tres giros bruscos en menos de 850 ms— son dos rayas anchas y un tambaleo.
Lo que vende la cara no son los píxeles sino hacia dónde miran. Los ojos son la única parte del cuerpo que tiene sus propias reglas de movimiento: parpadean un píxel en contra de la dirección en que sacudes la campana, y mientras nadas con el teclado se adelantan al giro antes que el cuerpo. La boca solo aparece cuando la medusa habla o emite una emoción; mientras habla, se abre y se cierra con un seno rápido. Nada de esto necesitó dibujar un solo fotograma de animación.
Tentáculos
No hay brazos orales ni sprites de tentáculos. Cada tentáculo es una cadena de cuentas cuadradas bajo integración de Verlet, anclada al 28% hacia abajo de la campana, con tres pasadas de restricción de distancia por fotograma para mantener las cuentas a su distancia de «separación». Lo que los hace sentir bajo el agua son tres ondas sinusoidales a tres frecuencias distintas —una deriva lenta, un vaivén más rápido y un rizo ponderado hacia la cola—, de modo que las raíces se mantienen tranquilas y las puntas ondulan. Mueve el cursor a través de ellos y se separan y arremolinan; detente y se asientan a su propio ritmo.
Los controles deslizantes de los tentáculos (cantidad 3–12, segmentos 4–18, gravedad, fricción, tensión, corriente, rizo, evitación) están expuestos directamente, y cuatro «preajustes de movimiento» de un clic —Quieto, Vivo, Salvaje, Deriva— no son más que cuatro combinaciones guardadas de esos seis números.
Color
Siete paletas de tres colores vienen por defecto —campana, tentáculos, ojos—, además de selectores de color libres para cada una:
| Paleta | Campana | Tentáculos | Ojos |
|---|---|---|---|
| océano | #8ae2ff | #8ae2ff | #303030 |
| atardecer | #ffb38a | #ff8fc7 | #3a2a2a |
| lima | #b6f5a0 | #8ce6c0 | #26331f |
| uva | #c7a2ff | #a98cff | #2c2640 |
| rosa | #ff9ec4 | #ffc0dd | #3a2630 |
| oro | #ffe08a | #ffd06a | #3a3320 |
| mono | #cfd8e3 | #aab4c2 | #20242b |
El color de la campana hace más que colorear la campana. El motor lo escribe en la variable de acento de la página (y el color de los tentáculos en el acento secundario), así que toda la interfaz del laboratorio se re-tematiza con la medusa que estés creando. El negro, el blanco, los grises y el rojo semántico nunca cambian; todo lo demás te sigue. Y, como veremos en la parte 3, también se convierte en el color de la luz que llevas.
Si la campana procedural no es suficiente, puedes desactivarla y pintar la tuya propia en una cuadrícula de 6×6 a 28×28, y pintar hasta ocho caras personalizadas encima. Trece de nuestros compañeros de equipo hicieron exactamente eso; sus medusas ahora viven en la escena de la oficina como NPC, cada una con su propio cuerpo, número de tentáculos, expresión y líneas de diálogo.
2. El aspecto pixelado es una resolución
La forma más común de conseguir un «aspecto pixelado» en la web es renderizar a resolución completa y posprocesar: ajustar colores, añadir una superposición de cuadrícula, quizá desenfocar y recuantizar. Nosotros hicimos lo contrario: el mundo es realmente diminuto. Se dibuja en un lienzo de una cuarta parte del tamaño de la ventana —una celda del mundo son cuatro píxeles de pantalla a la escala predeterminada— y se le indica al navegador que lo estire sin suavizado.
Cada llamada de dibujo del motor escribe valores de color empaquetados de 32 bits directamente en ese pequeño búfer: la campana, las cuentas de los tentáculos, las baldosas del terreno, incluso el texto. No hay ninguna ruta vectorial en ninguna parte de la capa del mundo, así que nada puede suavizarse por accidente. Las posiciones se redondean a la cuadrícula de celdas en cada anclaje: la parte superior de la campana, la fila de los ojos, cada baldosa del terreno, el desplazamiento de la cámara usado por la cuadrícula de iluminación.
Esto tiene una bonita consecuencia para el deslizador de «tamaño de píxel». Como cambia cuántos píxeles de pantalla cubre una celda del mundo (4 × 0,7 a 4 × 1,6), cambia tanto el grano de la imagen como el tamaño aparente de la medusa, en un solo movimiento. No hay un zoom separado.
Dos lienzos y una capa de texto real
Una decisión estructural que nos gusta especialmente: el mundo son dos lienzos con una capa HTML intercalada entre ellos. Los suelos y los títulos de las salas se pintan en el lienzo inferior. El texto estilo blog que vive dentro de la oficina es HTML normal, seleccionable y accesible, encima de eso. El lienzo superior —las ondas del agua, los objetos, los portales, la propia medusa— deja transparente cada celda no tocada, así que el texto se lee a través del agua vacía pero la medusa nada por encima.
El resultado es un texto que parece hundido en la escena en lugar de pegado sobre ella. El lienzo de fondo también se almacena en caché: solo se repinta cuando la cámara se mueve una celda entera o la escena cambia.
Dithering, no degradados
Donde la imagen necesita un degradado —sobre todo en la iluminación— usamos dithering ordenado en lugar de interpolación suave. Una matriz de Bayer de 4×4, indexada por coordenadas del mundo para que el patrón se quede quieto al desplazarte, rompe la luz en bandas planas y luego dispersa las costuras. Los «pasos de luz» se exponen como un deslizador (2–8, predeterminado 3). Con 2 el suelo parece un grabado en madera a dos tonos; con 8 está cerca de ser suave. Con tres es donde todavía parece hecho a mano.
La corrección de color —un tinte global y un ajuste de saturación— pasa por una tabla de búsqueda de 32×32×32, que discretiza silenciosamente la paleta a cinco bits por canal como efecto secundario. Todos los lienzos de vista previa de la interfaz tienen el suavizado desactivado; el único lugar donde el motor usa un desenfoque CSS es en las transiciones de la interfaz, nunca en el mundo.
3. El brillo es un campo de luz
Una medusa que no brilla no es una medusa. La implementación obvia —un halo radial suave mezclado sobre la escena, o una pasada de desenfoque— se descartó el primer día porque produce degradados suaves de subpíxel que se ven mal en una cuadrícula de píxeles dura. Un halo suave flota sobre el mundo; no le pertenece.
Así que en lugar de dibujar un brillo, iluminamos la sala. Cada luz —la medusa, las lámparas, los objetos brillantes— se estampa en un pequeño mapa de luz a una cuarta parte de la resolución del lienzo (una dieciseisava parte de la pantalla), con una caída (1 − d/r)²: no físicamente correcta, pero suave, barata y fácil de ajustar. El suelo y los muebles se iluminan entonces según cuánta luz les llega, en los mismos tres pasos planos con el mismo dithering. Se mantienen dos mapas —uno para el suelo y otro para los objetos con un radio 1,6× más ancho—, así que los objetos quedan dentro de un halo ligeramente mayor que el suelo bajo ellos.
La pasada de sombreado multiplica cada píxel por la luz ambiental más el campo de luz, limitado a un techo. Ese techo —1,25, es decir, la luz puede empujar un color hasta el 125 % de sí mismo y no más— es todo el presupuesto de «bloom». De ahí viene el núcleo ligeramente sobreexpuesto del brillo, y es la diferencia entre un farol y un destello de lente.
Hay tres detalles de implementación fáciles de pasar por alto:
- El color de la campana es también el color de la luz. Elige una medusa rosa y el suelo cercano se vuelve rosa; el mismo valor hexadecimal actualiza el color de acento del laboratorio.
- El color se normaliza por su canal máximo, así que una campana azul marino oscura sigue proyectando un tinte azul marino a plena intensidad en lugar de apenas iluminar la sala.
- El parpadeo se actualiza cada 8 fotogramas. Un ligero tartamudeo encaja mejor en este mundo de píxeles que un brillo perfectamente suave.
Una regla más evita que las lámparas se vean mal: cualquier cosa que emita luz queda excluida de ser iluminada. Los objetos con un componente de luz se dibujan con una máscara emisiva, y la pasada de sombreado copia esos píxeles sin tocarlos, así que una lámpara nunca se oscurece por su propia luz ambiental.
Los controles
Todo esto se expone en el panel Escena → Luz. Estos son los números con los que acabamos lanzándolo:
| Control | Rango | Predeterminado |
|---|---|---|
| Brillo ambiental | 0.2 – 1 | 0.55 |
| Color ambiental | — | #6d8fae |
| Pasos de luz | 2 – 8 | 3 |
| Techo de resaltado | 1 – 1.5 | 1.25 |
| Brillo de la medusa · radio | 8 – 80 | 32 |
| Brillo de la medusa · intensidad | 0 – 1.5 | 0.6 |
| Brillo de la medusa · parpadeo | 0 – 0.6 | 0.05 |
El suelo solo se vuelve a sombrear cuando cambian el nivel, los parámetros del shader o las posiciones de las luces; las luces fuera del viewport se descartan antes de estamparse; la tabla de corrección de color se almacena en caché. En la práctica, todo el mundo —fondo, simulación del agua, objetos, tentáculos, iluminación, medusa— cabe cómodamente en un solo fotograma de animación en un portátil, a una resolución en la que una ventana de 1440 px de ancho tiene solo 360 celdas de ancho.
4. Algunas cosas sobre cómo se mueve
No estrictamente sobre el aspecto, pero dan forma a cómo se lee el personaje, así que brevemente:
- El empuje del teclado se acumula y se amortigua, con un tope justo por debajo de una celda por fotograma. La velocidad también alimenta la deformación de la campana, así que una natación rápida la aplasta visiblemente contra la «corriente».
- Una muesca de la rueda introduce un impulso en la misma velocidad que el teclado y equivale a una celda de desplazamiento en estado estacionario.
- La cámara sigue despacio mientras nadas, más rápido mientras se asienta, y muy despacio durante 1,6 segundos tras un arrastre para que no vuelva de golpe.
- La velocidad horizontal fija una inclinación objetivo. Un muelle rígido y amortiguado la sigue y deja que la campana se pase ligeramente cuando se detiene.
- El cursor empuja las cuentas de los tentáculos dentro de 40 px. Dentro de 58 px también añade un remolino y un pulso radial, ponderados hacia la cola.
- Cada dos celdas de recorrido añade un pulso suave a la simulación de ondas en 2D. Esas ondas pueden activar objetos, que a su vez pueden propagar el pulso.
5. Por qué crear el personaje a partir de datos
No empezamos con una teoría sobre personajes de IA. Empezamos con un editor de medusas y luego notamos una ventaja práctica: los mismos parámetros que hacen que el personaje sea personalizable también lo hacen legible para un agente.
Los parámetros son más fáciles de usar para un agente
Una hoja de sprites es opaca para un modelo. Nuestra medusa son unos treinta números y algunos enums, lo suficientemente pequeños para un prompt, un blob JSON o una llamada a herramienta.
- Un agente puede leer los parámetros y saber cómo es la medusa de su usuario.
- Puede convertir «hazme una morada somnolienta con tentáculos largos» en un preset sin generar una nueva imagen. El NPC de cada compañero de equipo es una fila de datos en lugar de un recurso artístico.
- La expresión, el brillo y el movimiento son estado en tiempo de ejecución, así que la mascota puede verse concentrada mientras el agente trabaja o mareada cuando se confunde.
- Los parámetros pueden variar ampliamente mientras el resultado sigue pareciendo el mismo tipo de medusa.
Esa es la parte útil para AirJelly. La mascota puede actuar como el cuerpo del agente porque su apariencia y comportamiento son datos que el agente puede leer y cambiar. Nunca tenemos que pedirle a un modelo que edite una imagen opaca.
Por qué seguimos prefiriendo los píxeles (y el ASCII)
Hay una razón práctica. Un sprite de píxeles es una pequeña cuadrícula de índices de paleta; el arte ASCII ya es texto. Ambos pueden describirse de forma compacta y editarse celda por celda. Para los cambios precisos que necesitamos, un sprite de 16×16 simplemente deja menos margen a un modelo para improvisar que una pintura.
También hay una razón más subjetiva: nos gustan las cuadrículas rígidas. Son honestas sobre estar hechas por una máquina, y un error de un píxel a menudo resulta encantador en lugar de inquietante. Ese tono encaja con un producto sobre una IA que vive en tu ordenador. Puede que sea también la razón por la que las terminales resultan cómodas en las herramientas de IA.
Así que aquí es donde llegamos: un personaje descrito por números y dibujado sobre una cuadrícula. Da la casualidad de que parece retro, pero la parte útil es lo fácil que resulta tanto para una persona como para un agente cambiarlo con precisión.
Una última cosa. Si ajustas una medusa que te guste en el laboratorio, exporta sus parámetros y envíalos a xiaohua@airjelly.ai — la pondremos en el sitio. Trece ya viven en la oficina; siempre hay sitio para una más.


























