Skip to content

Vaynerov Technologies

Nous ne nous contentons pas de développer — nous façonnons chaque ligne de code et chaque pixel comme par enchantement.

Tous les articlesRécits de fabrication

Un musée où il y a d'autres gens

La version une était un musée pour une seule personne. La version deux est le même bâtiment, avec d'autres gens qui le traversent : quatre caractères pour entrer, jusqu'à huit visiteurs, des voix qui s'éteignent quand on s'éloigne — et pas un octet de tout cela gardé sur un serveur.

Edward AmirainFondateur, Vaynerov Technologies
Publié 15 min de lecture
Sur cette page
  1. Une salle est un canal, pas un enregistrement
  2. Un relais sans autorité
  3. Huit personnes en laine et en lin
  4. Des voix qui s'éteignent quand on s'éloigne
  5. Visiter sans qu'on écrive chez vous
  6. La couture laissée ouverte par la version une
  7. Le bâtiment lui-même s'est amélioré
  8. Ce qui est sorti sans bruit

Il y a six semaines, j'ai raconté un musée pour tout ce qui a une URL — le fetch sous garde, la cascade d'analyse, un plan qui ne bouge jamais. Ça marchait, et c'était un bâtiment pour exactement une personne. On pouvait coller un lien, accrocher une chose, parcourir ses propres salles, et ensuite la seule chose à faire de ce qu'on avait bâti, c'était de le décrire à quelqu'un.

La version deux est donc le même musée avec d'autres gens dedans. Vous ouvrez une salle, vous lisez quatre caractères à voix haute, et jusqu'à sept visiteurs de plus entrent dans votre bâtiment — sous forme de silhouettes que vous voyez d'un bout à l'autre d'une galerie, avec des voix qui baissent à mesure qu'elles dérivent vers la salle suivante. Le bâtiment lui-même s'est amélioré pendant que j'y étais : trois sortes de galerie au lieu d'une, de la sculpture sur socles, un ciel qui change selon le jour, et une visite guidée qui est enfin un vrai contrôleur de caméra.

Rien de tout cela n'est stocké nulle part. Cette contrainte a fait davantage de travail de conception que n'importe quelle fonctionnalité, alors elle passe en premier.

8
visiteurs dans une salle
0
octet stocké côté serveur
246
chaînes d'interface ×6 langues
303
tests unitaires

La forme de fonctionnement de Reliquary v2. Le deuxième chiffre est celui avec lequel la v1 était sortie ; le tenir une fois qu'on est plusieurs dans le bâtiment, c'est le sens de presque tout ce qui suit.

Une salle est un canal, pas un enregistrement

La façon évidente de construire la visite à plusieurs, c'est une table de salles : une ligne par salle, une liste des présents, un historique pour le chat. C'est aussi le point où votre musée ne quitte jamais votre navigateur devient discrètement …sauf les parties que nous gardons désormais. Il n'y a donc d'enregistrement de salle nulle part. Une salle est un topic de canal — vt:reliquary:v1:<CODE> — sur un relais de diffusion en temps réel, et le navigateur de l'hôte est la salle. Elle vit tant que la clé de présence de l'hôte reste sur le canal ; quand l'hôte ferme l'onglet, c'est fini.

N'avoir aucun enregistrement a une conséquence que je n'avais pas anticipée : il n'existe aucun moyen de demander si une salle existe. Un topic où personne ne se trouve s'abonne exactement aussi volontiers qu'un topic animé. Ce sont donc la présence et un délai qui répondent à la question — un invité qui arrive attend six secondes l'apparition d'une clé d'hôte, et ce délai est la définition de « cette salle n'existe pas ». Avant qu'un hôte ne prenne un code, la même absence l'oblige à rester 900 ms sur le topic pour voir si quelqu'un y habite déjà.

Le code fait quatre caractères, affiché sous la forme REL-7K2M. L'alphabet compte trente symboles : pas de 0 ni de O, pas de 1, de I ni de L, pas de U — les deux premières paires sont les confusions classiques à la lecture à voix haute, et le U disparaît pour qu'aucun tirage n'épelle quelque chose qu'un visiteur préférerait ne pas dire tout haut. Trente puissance quatre, cela fait 810 000 codes, tirés de crypto.getRandomValues. C'est de la confidentialité par obscurité, et elle l'annonce : deviner un code est la seule porte vers le musée de quelqu'un d'autre, alors l'hôte dispose d'une commande pour fermer la salle et chaque octet qui arrive est de toute façon revalidé.

Un relais sans autorité

L'identifiant qui fait entrer un navigateur sur ce canal est public par conception — les pages marketing le livrent déjà à chaque visiteur. La description honnête du transport, c'est un tuyau bête dans lequel quiconque détient quatre caractères peut envoyer n'importe quoi, et tout ce qui se trouve en aval est bâti sur cette hypothèse.

Chaque message est revalidé à l'arrivée, par type et par borne, dans une seule fonction qui constitue toute la frontière de confiance de la fonctionnalité. Les œuvres qui arrivent d'un pair repassent par sanitizeExhibit — la fonction même par laquelle le stockage du visiteur est filtré, importée et jamais recopiée pour qu'elle ne dérive pas. Une URL refusée depuis le disque est refusée depuis le fil. L'identifiant de l'expéditeur est une affirmation plutôt qu'un titre, et il n'y a délibérément aucune couche de signature, contrairement au fil des échecs 4D où un coup falsifié pourrait voler une partie. Ici, il n'y a rien à voler.

L'expéditeur n'est jamais l'assainisseur. L'accrochage d'un invité est une demande ; l'hôte le réassainit, l'applique via son propre magasin, et rediffuse ce qui s'est réellement posé.

Ce qu'il a fallu défendre, c'est l'amplification. Un hello de trente octets qui achète une resynchronisation de soixante œuvres est un levier braqué sur la salle : tout message dont la réponse coûte plus cher que ce que porte le message a donc un plancher par expéditeur — deux secondes entre resynchronisations, 400 ms entre lignes de chat, trois secondes entre demandes d'accrochage. Taper est plus lent que 400 ms ; une boucle, non. La synchronisation est découpée à 48 Ko ou 24 éléments, au premier des deux — et une collection vide émet quand même un morceau portant une liste vide, parce qu'un invité qui ne reçoit rien ne peut pas distinguer « l'hôte ne possède rien » de « la synchro est encore en route ».

Le canal porte des présentations et rien qui ait autorité. L'audio n'y touche jamais : le relais remet à deux navigateurs les blobs de connexion opaques l'un de l'autre, et le média passe en direct.

La position est le seul message qui se répète — dix fois par seconde, par visiteur, tant que la salle est ouverte — elle est donc quantifiée au centimètre et au milliradian. Ce n'est pas seulement de la compression : le quantificateur est aussi le détecteur d'inactivité, puisque deux poses qui se quantifient vers les mêmes entiers sont la même pose, et que rester immobile arrête donc la diffusion sans test séparé.

Huit personnes en laine et en lin

Les spécifications de la v1 portaient deux lignes en tête : aucune nouvelle dépendance npm, aucun asset binaire. La seconde a décidé de ce qu'est un visiteur. Pas de téléversement, pas de personnages téléchargés — un visiteur, ce sont quatre petits entiers et un nom, et toute la sortie du vestiaire tient dans une charge utile de présence.

Huit palettes, nommées d'après l'étoffe plutôt que d'après la couleur : ardoise, chameau, prune, mousse, rouille, encre, lin, vert-de-gris. Le vocabulaire de la galerie, c'est le noyer, le badigeon de chaux et le laiton sous un soleil bas, et chaque tenue se tient à l'intérieur de cette plage au lieu de lui résister. Exactement une palette tend vers le turquoise, deux crans plus bas en chroma — un visiteur en manteau turquoisé, pas une source lumineuse qui traverse la salle. Ici, le turquoise reste un scalpel.

Les quatre formes de tête ne sont qu'une seule courbe. Une superellipse — |y/ry|^n + |x/rx|^n = 1 — est une ellipse à n = 2 et s'approche du rectangle quand n grandit ; ronde, ovale, carrée et crête sont quatre jeux de paramètres de cette courbe, la tête carrée à 3,6, un cube de révolution adouci plutôt qu'une boîte à angles. Une fonction émet la silhouette, la figure 3D la tourne, l'aperçu plat du vestiaire la reflète en tracé — le sélecteur ne peut donc pas promettre une tête que la figure ne tiendra pas. La taille tient dans une bande étroite, de 0,9× à 1,1× d'une figure de 1,70 m.

La décision dont je pensais devoir débattre avec moi-même, c'était la collision. Les pairs sont des fantômes : aucun avatar ne fournit de collisionneur et le contrôleur de marche n'apprend jamais leur existence. Ce que nous dessinons est une estimation — une supposition lissée par ressort sur l'endroit où quelqu'un se trouvait deux ou trois cents millisecondes plus tôt — et être bousculé par une estimation est pire que traverser une personne réellement présente. Et une porte fait 2,3 m de large : un seul visiteur immobile planté dedans, solide, condamne toute une aile du musée d'un inconnu sans aucun moyen de lui demander de se pousser. Se superposer, c'est deux secondes de gêne ; se retrouver coincé, c'est une visite cassée.

Des voix qui s'éteignent quand on s'éloigne

Le chat texte a pris une matinée. La voix, c'est ce qui transforme une page partagée en salle partagée. L'audio circule de navigateur à navigateur par des connexions directes entre pairs ; le canal ne fait que remettre à deux visiteurs les blobs de session opaques l'un de l'autre. C'est cela qui fixe le plafond à huit, hôte compris : un maillage complet de huit, c'est 28 connexions et sept flux montants par personne, à peu près ce que supportent le lien montant d'un portable et l'encodeur d'un téléphone. Monter plus haut supposerait un serveur média, pas une constante plus grande.

Les deux extrémités d'une paire apprennent l'existence l'une de l'autre par le même événement de présence, au même instant : toutes deux tentent donc d'ouvrir la conversation et toutes deux se bloquent — le classique glare. Le correctif coûte une comparaison : le pair dont l'identifiant se classe en premier propose, l'autre attend. Les identifiants de pair font 32 caractères hexadécimaux, l'ordre est donc total et identique des deux côtés. La voix s'active visiteur par visiteur, si bien qu'un répondant qui n'a rien entendu depuis 2,5 secondes se promeut et propose quand même.

Puis la partie qu'on entend vraiment. Une voix est à plein gain en deçà de deux mètres — la distance de conversation — et décroît ensuite en distance inverse simple. L'inverse physique, cependant, ne réduit jamais personne au silence : à quatorze mètres, une voix est encore autour de 14 % de gain, et sept de ces voix sous le fond sonore de la salle donnent un marmonnement qui sonne comme une panne plutôt que comme un musée. La courbe est donc fenêtrée jusqu'à exactement zéro sur son dernier segment par un smoothstep, plat aux deux bouts pour que personne ne claque en s'éloignant. Les salles ici font une dizaine de mètres, ce qui place l'extinction entre le fond de cette salle et deux salles plus loin.

Les deux axes sont linéaires depuis zéro. La ligne pointillée est la loi inverse seule, encore audible au bord lointain ; la courbe livrée, c'est cette loi multipliée par une fenêtre, et c'est ce qui donne un sens à la distance dans un bâtiment de cette taille.

La panoramique est la composante latérale du relèvement, plafonnée à ±0,8, et elle se replie vers le centre en deçà d'un mètre pour que deux visiteurs qui se traversent ne fouettent pas de gauche à droite dans vos oreilles.

La voix fait tourner son propre contexte audio au lieu d'emprunter celui du moteur de jeux, qui se suspend dès que le document est masqué : correct pour des pas, faux pour une conversation. Elle obéit tout de même à la même coupure du son, parce que quelqu'un qui a coupé le son du musée voulait dire que ce soit silencieux, pas que les carillons soient silencieux. Le push-to-talk est sur V et coupe la piste du micro, pas la connexion.

Le résidu, nommé plutôt qu'enfoui : il n'y a pas encore de serveur relais. Deux navigateurs dont les réseaux refusent de se rencontrer directement ne se connectent jamais. Cette paire est marquée indisponible dans la liste des visiteurs et conserve le chat texte — moins bien qu'une voix qui marche, bien mieux qu'un indicateur qui tourne sans jamais aboutir.

Visiter sans qu'on écrive chez vous

Un invité regarde la collection de quelqu'un d'autre, et la promesse de la dépêche de lancement, c'est que votre musée tient dans une clé de votre propre navigateur. Une visite affiche donc un monde entièrement distinct de celui que détient votre clé, et rien n'est écrit dans votre stockage pendant que vous êtes sorti. La bascule voyage sous un voile dans les deux sens, et elle voile immédiatement, avant que la synchronisation découpée ne soit arrivée — l'autre solution consisterait à laisser un invité debout un instant dans sa propre galerie, à se demander à qui sont ces tableaux.

La lecture seule s'est révélée être une série de petits arbitrages plutôt qu'un unique interrupteur. Le tiroir de gestion garde sa liste et son aller-jusqu'à, mais retirer et vider ont disparu. La barre de collage reste et devient une demande : l'analyse a toujours lieu dans le navigateur de l'invité, le résultat assaini voyage, l'hôte assainit à nouveau et répond par un verdict qui arrive sous forme de notification. Quand l'accrochage est coupé, la barre est désactivée et dit pourquoi au lieu de disparaître, parce qu'une commande absente est indiscernable d'une page cassée.

La couture laissée ouverte par la version une

La dépêche de lancement se terminait sur une promesse : une visite guidée, « esquissée derrière la même couture de contrôleur que le mode marche ». Pendant un temps, cette visite a été ce que ces choses-là deviennent d'habitude — une chaîne d'interpolations de caméra à usage unique déclenchées depuis la boucle de rendu, ce qui voulait dire que la caméra avait deux propriétaires selon la minute.

Il existe désormais une seconde implémentation. Le rail est une liste d'arrêts précalculée : chaque œuvre dans l'ordre d'accrochage, chacune à la pose de vue frontale qu'utilise déjà la marche automatique. Entre deux arrêts, la caméra suit la polyligne des points de passage des portes au lieu d'interpoler à travers un mur, s'attarde 3,6 secondes, et reboucle à la fin. Le regard au glisser pendant le trajet ajoute un décalage borné par-dessus l'orientation propre du rail — il retombe vers le centre pendant le déplacement, si bien que l'œuvre suivante arrive toujours centrée, et il tient pendant l'arrêt. Toucher les touches de déplacement, voilà la vraie reprise en main, et elle vous rend à la marche à la pose courante. La meilleure conséquence d'un contrôleur plutôt que d'une animation : ouvrir une œuvre met désormais la visite en pause au lieu d'y mettre fin.

La minicarte sort du même module de plan que le bâtiment — l'entrée en haut, les galeries qui descendent le panneau dans l'ordre où on les parcourt. Cliquez sur une salle ou sur un point, et la machinerie existante de marche-ou-voile vous y emmène. Les autres visiteurs apparaissent en points, chacun dans sa couleur d'accent ; le seul turquoise du panneau est le secteur qui indique la direction de votre regard.

Le bâtiment lui-même s'est amélioré

La v1 avait une seule recette de galerie : une longue salle avec les œuvres alternant sur deux murs nus. C'est une bonne salle, et à la trentième œuvre c'est la seule salle où vous ayez jamais mis les pieds. Il y a maintenant trois archétypes, alternés selon l'indice de salle — la longue salle, un salon (une pièce d'apparat plus large, à pilastres d'angle sous un oculus de plafond) et une galerie à niches, où de faibles avancées sortent des murs latéraux entre les accrochages, si bien que chaque paire d'œuvres se tient dans sa propre travée.

L'invariant qui a rendu tout cela délicat est celui sur lequel repose le bâtiment entier : ajouter l'œuvre N+1 ne doit jamais déplacer les œuvres 1 à N. Tout ce qu'un archétype change est donc une fonction pure de l'indice de salle, et la salle 0 est délibérément la longue salle avec les nombres d'origine, de sorte que chaque musée bâti en juin reproduit exactement la géométrie qu'il avait. Les œuvres sans image s'accrochaient autrefois comme une plaque au mur ; elles se dressent maintenant en stèle gravée sur un socle, à la position d'emplacement identique.

Et le ciel s'est mis à bouger. Trois ambiances — heure dorée, un matin pâle, un crépuscule d'heure bleue — sont tirées d'une roue pondérée à dix cases par un hachage de la date du calendrier local. L'heure dorée conserve cinq des dix parce que c'est le visage de la maison ; vous avez un temps pour toute la journée, un autre demain, et deux personnes du même fuseau voient le même musée. Chaque ambiance porte tout le contrat d'éclairage, pas seulement le ciel : le crépuscule relève l'auto-luminescence chaude du plâtre au lieu de la baisser, parce que le mur au-dessus d'une porte n'est atteint ni par le soleil bas, ni par les spots des œuvres.

Ce qui est sorti sans bruit

Une sonde WebGL tourne désormais avant le montage du canvas, et une absence véritable reçoit une affiche localisée au lieu d'un rectangle vide — absence véritable uniquement, jamais classe d'appareil : un téléphone dont le WebGL fonctionne garde le musée entier, arpentable. La perte de contexte est câblée à un état de récupération et à un remontage propre.

Les œuvres sont des maillages : il n'y a donc rien vers quoi tabuler. Le motif qui convient à un canevas où l'on regarde au glisser, c'est un curseur sur l'ordre d'accrochage : [ et ] parcourent les œuvres, la caméra marche ou se téléporte sous voile jusqu'à chacune, et chacune est annoncée poliment. Enter inspecte — et sans curseur défini, il choisit l'œuvre la plus proche de l'endroit où vous vous tenez.

Et le musée a des tests, maintenant : 303 répartis sur 11 fichiers, contre aucun. C'est de l'architecture plus que de la vertu : le protocole, les codes de salle, les maths de la voix, le plan et le rail sont des modules purs sans React, sans three.js et sans réseau dedans, et c'est pour cela que pourquoi la voix de cette personne sort-elle du mauvais côté est un test et non un après-midi dans un navigateur. L'interface est passée de 87 chaînes à 246, dans les six langues, parité vérifiée.

v1 — juinv2 — aujourd'hui
Visiteurs1jusqu'à 8, hôte compris
Voixmaillage de proximité, direct entre pairs
Visite guidéeune chaîne d'interpolations de caméraun contrôleur de rail ; le focus la met en pause
Salles de galerieune seule recette de salle3 archétypes, alternés selon l'indice de salle
Cielheure dorée3 ambiances, une par jour du calendrier
Lots sans imageune plaque au murune stèle sur un socle
S'orientermarcher, ou la liste de gestionminicarte du plan avec les points des pairs
Chaînes d'interface87 ×6 langues246 ×6 langues
Tests unitaires0303 sur 11 fichiers
Stocké côté serveurrientoujours rien

La dernière ligne est la thèse. Tout ce qui se trouve au-dessus, c'est ce qu'il faut pour que la dernière ligne reste vraie dès qu'il y a plus d'une personne dans le bâtiment.

Le modèle de confidentialité de la version une était trop simple pour qu'on puisse le rater : pas de base de données, donc rien à perdre. La visite à plusieurs est d'ordinaire l'endroit où cette phrase se fait amender — il vous faut une salle quelque part, une liste des présents quelque part, un peu d'historique pour que le chat défile. Il se trouve que non. Il vous faut un canal, un code assez court pour être dit à voix haute, et la discipline de revalider chaque octet qui y arrive, parce qu'un relais sans autorité n'est sûr que si rien en aval ne lui fait confiance.

Ce à quoi je ne m'attendais pas, c'est à quel point le bâtiment s'est amélioré par effet de bord. Dès qu'une salle peut contenir d'autres gens, on remarque que toutes les galeries se ressemblent, que le ciel ne change jamais, que les lots sans image s'excusent d'eux-mêmes contre un mur. Seul, cela ne dérange personne. On ne le voit que lorsque quelqu'un d'autre se tient dedans.