Ouvrez le Jellyfish Lab et vous rencontrerez notre mascotte nageant à travers le bureau, avec des tentacules perlés derrière elle et une tache de lumière colorée sur le sol. On dirait un sprite, mais elle est dessinée de zéro pendant que la page s'exécute.
La forme de la cloche, le visage, les tentacules, le grain pixel et la lueur proviennent tous d'un petit ensemble de paramètres. Chacun est exposé sous forme de curseur, aussi l'article suit-il des contrôles que vous pouvez essayer vous-même dans le lab.
- La cloche est calculée ligne par ligne. Cinq formes de cloche utilisent cinq courbes de largeur différentes ; un visage ne nécessite qu'un à trois pixels par œil.
- Le monde est rendu dans un canvas d'un quart de la taille de la fenêtre d'affichage, puis étiré à nouveau sans lissage.
- La lueur provient d'une carte de lumière à l'échelle 1/16 avec une atténuation au carré, des niveaux de lumière par paliers et une matrice de tramage 4×4.
Chaque image ici est une capture d'écran du lab. Nous avons utilisé la même méduse pêche tout du long, avec le préréglage de visage dessiné à la main de l'auteur. Rien n'a été maquetté pour l'article.
1. Le personnage est une fonction
Un personnage en pixel art commence généralement dans un éditeur d'images. Le nôtre a commencé par une question : quelle est la plus petite description de « une méduse » qui permette encore à quelqu'un de faire ressembler la sienne à autre chose que la mienne ?
La réponse s'est avérée être une courbe de largeur. La cloche est dessinée une ligne à la fois, de haut en bas, et la seule chose qui change entre une cloche, un dôme, un blob, une lanterne et une goutte est la fonction qui indique la largeur de chaque ligne. Un dôme s'ouvre vite avec une épaule douce ; un blob est le plus large au milieu et pincé aux deux extrémités ; une lanterne s'ouvre dans le premier sixième et se resserre sur le dernier cinquième ; une goutte atteint sa pleine largeur environ aux trois quarts vers le bas puis se rétrécit ; une cloche simple est un évasement droit.
Deux petites règles se cachent sous la courbe. Le curseur « rondeur » (1–4) décide du nombre de lignes que la couronne met à s'évaser. Chaque ligne a aussi une largeur impaire. Cette colonne centrale compte plus qu'il n'y paraît : les yeux et la bouche peuvent se placer pile au centre au lieu d'être décalés d'un demi-pixel.
À partir de là, le reste du corps suit. Largeur, largeur du haut, hauteur, rondeur et écrasement sont cinq curseurs. Comme la forme est calculée plutôt que stockée, la cloche peut aussi se déformer : quand vous la faites glisser, la ligne que vous avez saisie s'incline plus que les lignes éloignées de votre curseur. Cette seule règle est ce qui lui donne l'impression d'être en caoutchouc souple plutôt qu'une carte rigide.
La rotation est gérée de la même manière, mais à l'envers : au lieu de faire tourner les pixels vers l'avant (ce qui laisserait des trous), le moteur de rendu parcourt la boîte englobante pivotée, ramène chaque cellule d'écran dans l'espace de la cloche et demande « est-ce à l'intérieur de la ligne r ? ». Plus proche voisin, pas d'anticrénelage, pour qu'une méduse inclinée reste aussi nette qu'une méduse droite.
Le visage
Les yeux se situent à 54 % de la hauteur de la cloche, espacés de 18 % de la largeur de la cloche. Ces deux ratios constituent tout le système de mise en page. L'expression n'est alors que le choix des un à trois pixels allumés : un point, un tiret, un ^, ou un point décalé d'un pixel vers le haut ou vers l'intérieur. Curieux, c'est un œil légèrement plus haut que l'autre. Coincé, c'est les deux yeux poussés vers le nez. Endormi, c'est deux tirets. Étourdi — déclenché par trois virages brusques en moins de 850 ms — c'est deux larges tirets et un tremblement.
Ce qui vend le visage, ce ne sont pas les pixels mais là où ils regardent. Les yeux sont la seule partie du corps qui possède ses propres règles de mouvement : ils font un écart d'un pixel à l'encontre de la direction dans laquelle vous tirez la cloche, et pendant que vous nagez au clavier, ils amorcent le virage avant le corps. La bouche n'apparaît que lorsque la méduse parle ou exprime une émotion ; pendant qu'elle parle, elle s'ouvre et se ferme sur un sinus rapide. Rien de tout cela n'a nécessité une seule image d'animation pour être dessiné.
Tentacules
Il n'y a ni bras oraux ni sprites de tentacules. Chaque tentacule est une chaîne de perles carrées soumise à l'intégration de Verlet, ancrée à 28 % sous la cloche, avec trois passes de contrainte de distance par image pour maintenir les perles à leur distance d'« écart ». Ce qui leur donne une sensation sous-marine, ce sont trois ondes sinusoïdales à trois fréquences différentes — une dérive lente, un balancement plus rapide et une courbure pondérée vers la queue — pour que les racines restent calmes et que les pointes ondulent. Faites passer le curseur à travers elles et elles s'écartent et tourbillonnent ; arrêtez-vous et elles se stabilisent à leur propre rythme.
Les curseurs de tentacules (nombre 3–12, segments 4–18, gravité, friction, tension, courant, courbure, évitement) sont exposés directement, et quatre « préréglages de mouvement » en un clic — Immobile, Vif, Sauvage, Dérive — ne sont rien de plus que quatre combinaisons enregistrées de ces six nombres.
Couleur
Sept palettes de trois couleurs sont fournies par défaut — cloche, tentacules, yeux — plus des sélecteurs de couleur libres pour chacune :
| Palette | Cloche | Tentacules | Yeux |
|---|---|---|---|
| océan | #8ae2ff | #8ae2ff | #303030 |
| coucher de soleil | #ffb38a | #ff8fc7 | #3a2a2a |
| citron vert | #b6f5a0 | #8ce6c0 | #26331f |
| raisin | #c7a2ff | #a98cff | #2c2640 |
| rose | #ff9ec4 | #ffc0dd | #3a2630 |
| or | #ffe08a | #ffd06a | #3a3320 |
| mono | #cfd8e3 | #aab4c2 | #20242b |
La couleur de la cloche ne fait pas que colorer la cloche. Le moteur l'écrit dans la variable d'accent de la page (et la couleur des tentacules dans l'accent secondaire), si bien que toute l'interface du labo se re-thématise selon la méduse que vous créez. Le noir, le blanc, les gris et le rouge sémantique ne changent jamais ; tout le reste vous suit. Et, comme nous le verrons dans la partie 3, elle devient aussi la couleur de la lumière que vous portez.
Si la cloche procédurale ne suffit pas, vous pouvez la désactiver et peindre la vôtre sur une grille de 6×6 à 28×28, et peindre jusqu'à huit visages personnalisés par-dessus. Treize de nos coéquipiers l'ont fait exactement ; leurs méduses vivent désormais dans la scène du bureau en tant que PNJ, chacune avec son propre corps, son nombre de tentacules, son expression et ses répliques.
2. L'aspect pixel est une résolution
La façon la plus courante d'obtenir un « aspect pixel » sur le web est de rendre en pleine résolution puis de post-traiter — figer les couleurs, ajouter une grille en surimpression, éventuellement flouter et re-quantifier. Nous avons fait l'inverse : le monde est réellement minuscule. Il est dessiné dans un canvas d'un quart de la taille de la fenêtre — une cellule du monde fait quatre pixels d'écran à l'échelle par défaut — et le navigateur est chargé de l'étirer sans lisser.
Chaque appel de dessin du moteur écrit des valeurs de couleur 32 bits compactées directement dans ce petit tampon — la cloche, les perles des tentacules, les tuiles du terrain, même le texte. Il n'y a aucun tracé vectoriel nulle part dans la couche du monde, donc rien ne peut être anti-aliasé par accident. Les positions sont arrondies à la grille de cellules à chaque point d'ancrage : le sommet de la cloche, la rangée des yeux, chaque tuile de terrain, le décalage de caméra utilisé par la grille d'éclairage.
Cela a une conséquence agréable pour le curseur « taille des pixels ». Comme il change le nombre de pixels d'écran couverts par une cellule du monde (4 × 0,7 à 4 × 1,6), il change à la fois le grain de l'image et la taille apparente de la méduse, d'un seul geste. Il n'y a pas de zoom séparé.
Deux canvas et une couche de vrai texte
Une décision structurelle que nous aimons particulièrement : le monde est constitué de deux canvas avec une couche HTML prise en sandwich entre eux. Les sols des pièces et les titres sont peints sur le canvas du bas. Le texte de style blog qui vit dans le bureau est du HTML ordinaire, sélectionnable et accessible, par-dessus. Le canvas du haut — ondulations de l'eau, objets, portails, la méduse elle-même — laisse chaque cellule non touchée transparente, si bien que le texte se lit à travers l'eau vide mais la méduse nage par-dessus.
Le résultat est un texte qui semble immergé dans la scène plutôt que collé par-dessus. Le canvas d'arrière-plan est aussi mis en cache : il ne se redessine que lorsque la caméra se déplace d'une cellule entière ou que la scène change.
Tramage, pas dégradés
Là où l'image a besoin d'un dégradé — surtout dans l'éclairage — nous utilisons un tramage ordonné plutôt qu'une interpolation lisse. Une matrice de Bayer 4×4, indexée par les coordonnées du monde pour que le motif reste en place quand vous défilez, découpe la lumière en bandes plates puis disperse les coutures. « Étapes de lumière » est exposé comme curseur (2–8, par défaut 3). À 2, le sol ressemble à une gravure sur bois en deux tons ; à 8, il est proche du lisse. C'est à trois qu'il a encore l'air fait main.
L'étalonnage des couleurs — une teinte globale et un tirage de saturation — passe par une table de correspondance 32×32×32, qui quantifie discrètement la palette à cinq bits par canal comme effet secondaire. Chaque canvas d'aperçu de l'interface a le lissage désactivé ; le seul endroit où le moteur utilise un flou CSS est sur les transitions de l'interface, jamais sur le monde.
3. La lueur est un champ de lumière
Une méduse qui ne luit pas n'est pas une méduse. L'implémentation évidente — un halo radial doux mélangé par-dessus la scène, ou une passe de flou — a été écartée dès le premier jour parce qu'elle produit des dégradés lisses sous-pixel qui rendent mal sur une grille de pixels dure. Un halo lisse flotte au-dessus du monde ; il ne lui appartient pas.
Donc au lieu de dessiner une lueur, nous éclairons la pièce. Chaque lumière — la méduse, les lampes, les objets lumineux — est estampillée dans une petite carte de lumière au quart de la résolution du canvas (un seizième de l'écran), avec une atténuation (1 − d/r)² : pas physiquement correcte, mais douce, peu coûteuse et facile à régler. Le sol et le mobilier sont ensuite éclaircis selon la quantité de lumière qui les atteint, dans les mêmes trois étapes plates avec le même tramage. Deux cartes sont conservées — une pour le sol et une pour les objets avec un rayon 1,6× plus large, afin que les accessoires baignent dans un halo légèrement plus grand que le sol sous eux.
La passe d'ombrage multiplie chaque pixel par l'ambiante plus le champ de lumière, borné à un plafond. Ce plafond — 1,25, ce qui signifie que la lumière peut pousser une couleur à 125 % d'elle-même et pas au-delà — constitue tout le budget de « bloom ». C'est de là que vient le cœur légèrement surexposé de la lueur, et c'est la différence entre une lanterne et un flare d'objectif.
Trois détails d'implémentation sont faciles à manquer :
- La couleur de la cloche est aussi la couleur de la lumière. Choisissez une méduse rose et le sol alentour devient rose ; la même valeur hexadécimale met à jour la couleur d'accent du labo.
- La couleur est normalisée par son canal de crête, si bien qu'une cloche bleu marine foncé projette quand même une teinte bleu marine pleine puissance au lieu d'éclairer à peine la pièce.
- Le scintillement se met à jour toutes les 8 images. Un léger saccadé convient mieux à ce monde de pixels qu'un miroitement parfaitement lisse.
Une règle de plus empêche les lampes d'avoir l'air fausses : tout ce qui émet de la lumière est exclu d'être éclairé. Les objets dotés d'une composante lumineuse sont dessinés avec un masque émissif, et la passe d'ombrage recopie ces pixels tels quels — ainsi une lampe n'est jamais assombrie par sa propre ambiante.
Les réglages
Tout cela est exposé dans le panneau Scène → Lumière. Voici les valeurs que nous avons fini par livrer :
| Contrôle | Plage | Par défaut |
|---|---|---|
| Luminosité ambiante | 0.2 – 1 | 0.55 |
| Couleur ambiante | — | #6d8fae |
| Étapes de lumière | 2 – 8 | 3 |
| Plafond de surbrillance | 1 – 1.5 | 1.25 |
| Lueur de la méduse · rayon | 8 – 80 | 32 |
| Lueur de la méduse · intensité | 0 – 1.5 | 0.6 |
| Lueur de la méduse · scintillement | 0 – 0.6 | 0.05 |
Le sol n'est ré-ombré que lorsque le niveau, les paramètres du shader ou les positions des lumières changent ; les lumières hors du viewport sont éliminées avant d'être estampillées ; la table d'étalonnage est mise en cache. En pratique, le monde entier — arrière-plan, simulation de l'eau, objets, tentacules, éclairage, méduse — tient confortablement dans une seule image d'animation sur un portable, à une résolution où une fenêtre de 1440 px de large ne fait que 360 cellules de large.
4. Quelques choses sur la façon dont ça bouge
Pas strictement sur l'apparence, mais elles façonnent la lecture du personnage, donc brièvement :
- La poussée au clavier monte et s'amortit, plafonnée à juste sous une cellule par image. La vitesse alimente aussi la déformation de la cloche, si bien qu'une nage rapide l'écrase visiblement contre le « courant ».
- Un cran de molette injecte une impulsion dans la même vélocité que le clavier et équivaut à une cellule de déplacement en régime permanent.
- La caméra suit lentement pendant que vous nagez, plus vite pendant la stabilisation, et très lentement pendant 1,6 seconde après un glisser pour ne pas revenir d'un coup.
- La vélocité horizontale fixe une inclinaison cible. Un ressort rigide et amorti la suit et laisse la cloche dépasser légèrement quand elle s'arrête.
- Le curseur repousse les perles des tentacules à moins de 40 px. À moins de 58 px, il ajoute aussi un tourbillon et une pulsation radiale, pondérés vers la queue.
- Tous les deux déplacements de cellule ajoutent une impulsion douce à la simulation d'onde 2D. Ces ondes peuvent déclencher des objets, qui peuvent à leur tour transmettre l'impulsion.
5. Pourquoi créer le personnage à partir de données
Nous n'avons pas commencé avec une théorie sur les personnages IA. Nous avons commencé avec un éditeur de méduses, puis avons remarqué un avantage pratique : les mêmes paramètres qui rendent le personnage personnalisable le rendent aussi lisible pour un agent.
Les paramètres sont plus faciles à utiliser pour un agent
Une planche de sprites est opaque pour un modèle. Notre méduse se résume à une trentaine de nombres et quelques énumérations, assez petits pour un prompt, un blob JSON ou un appel d'outil.
- Un agent peut lire les paramètres et savoir à quoi ressemble la méduse de son utilisateur.
- Il peut transformer « fais-moi une méduse violette somnolente avec de longs tentacules » en un préréglage sans générer une nouvelle image. Le PNJ de chaque coéquipier est une ligne de données plutôt qu'une ressource graphique.
- L'expression, la lueur et le mouvement sont des états d'exécution, si bien que la mascotte peut paraître concentrée pendant que l'agent travaille ou étourdie lorsqu'il s'embrouille.
- Les paramètres peuvent varier considérablement tandis que le résultat ressemble toujours au même type de méduse.
C'est la partie utile pour AirJelly. La mascotte peut servir de corps à l'agent parce que son apparence et son comportement sont des données que l'agent peut lire et modifier. Nous n'avons jamais besoin de demander à un modèle de modifier une image opaque.
Pourquoi nous aimons toujours les pixels (et l'ASCII)
Il y a une raison pratique. Un sprite en pixels est une petite grille d'indices de palette ; l'art ASCII est déjà du texte. Les deux peuvent être décrits de façon compacte et modifiés cellule par cellule. Pour les changements précis dont nous avons besoin, un sprite de 16×16 laisse simplement moins de marge d'improvisation à un modèle qu'une peinture.
Il y a aussi une raison plus subjective : nous aimons les grilles rigides. Elles assument honnêtement d'être faites par une machine, et une erreur d'un pixel paraît souvent charmante plutôt que troublante. Ce ton convient à un produit qui parle d'une IA vivant sur votre ordinateur. C'est peut-être aussi pourquoi les terminaux semblent à l'aise dans les outils d'IA.
Voilà donc où nous avons abouti : un personnage décrit par des nombres et dessiné sur une grille. Il se trouve qu'il a un look rétro, mais la partie utile, c'est la facilité avec laquelle une personne comme un agent peuvent le modifier avec précision.
Une dernière chose. Si vous réglez une méduse qui vous plaît dans le labo, exportez ses paramètres et envoyez-les à xiaohua@airjelly.ai — nous la mettrons sur le site. Treize vivent déjà au bureau ; il y a toujours de la place pour une de plus.


























