The operator plans, the room is not tracked live: removing a person from the retained plan must free her chair and leave every other seat where it is. With seats assigned, a table list may now hold null before its last occupant, seat 1 included, and never end on it; without assignment no list holds null. Validation, canonical form, patches and the named form handle it, and the delivered demos keep their bytes. Checked: 1549 node and 58 node-long tests; depot.js, journal.js at 100 %. --- FR --- [ADD] stockage : chaise vide dans une liste de table aux sièges attribués L'opérateur planifie, la salle ne se suit pas en direct : retirer une personne du plan retenu doit libérer sa chaise et laisser tout autre siège à sa place. Sièges attribués, une liste de table peut porter null avant son dernier occupant, siège 1 compris, jamais à la fin ; sans attribution, aucune liste ne porte null. Contrôle, forme canonique, correctifs et forme nommée le traitent, et les démonstrations livrées gardent leurs octets. Vérifié : 1549 node et 58 node-long ; depot.js, journal.js à 100 %. Assisted-by: Claude Opus 5.5
292 KiB
Gestion table tournante Libre — spécification
Exécutable livré : gestion_table_tournante_libre_v<AAAA>_<MM>_<JJ>_<NN>.exe
— un gabarit, jamais un nom figé : le nom porte la date de la livraison
(§ 18.2).
1. Objet
Un logiciel qui place les personnes d'un événement autour de tables et les fait tourner sur plusieurs tours, de sorte que chacune rencontre le plus de monde possible. Il propose plusieurs placements comparables plutôt qu'une réponse unique, mesure chacun par les mêmes indicateurs, et dit à l'opérateur ce qu'une configuration rend inévitable avant qu'il ne cherche en vain.
Il fonctionne hors ligne, sans serveur, et se livre comme un exécutable.
2. Contexte d'usage
L'opérateur organise un souper, un colloque, un banquet. Il travaille sur un portable, dans une salle, souvent sans réseau fiable. Il prépare son plan la veille et le retouche sur place quand des gens manquent ou s'ajoutent.
Les conséquences de ce contexte sont des exigences :
- Aucune connexion n'est requise, à aucun moment, pas même au premier démarrage.
- Aucune installation de serveur ni de base de données. L'exécutable se copie et se lance.
- Rien ne sort de la machine. Pas de télémétrie, pas de service distant.
- Les données se copient, se sauvegardent et se transmettent comme des fichiers ordinaires.
- L'interface est en français. Une seule langue dans la première itération ; le mécanisme de traduction est néanmoins posé dès le départ (§ 14.6), parce que retrouver les chaînes après coup coûte dix fois plus.
2.1 Le parcours de l'opérateur
Cette section ne pose aucune exigence nouvelle : elle met dans l'ordre où une
soirée se prépare des exigences dispersées sur dix sections. Elle a un
consommateur nommé — GUIDE-USAGE.md (§ 16.1) suit ces douze étapes, et le
scénario de pilotage du § 19.3 les implémente, de sorte que le guide, la
spécification et le logiciel ne peuvent pas diverger sur l'ordre.
Le parcours est un ordre de lecture, pas une séquence imposée. Le logiciel n'enchaîne aucun assistant et n'exige jamais de recommencer au début : un assistant linéaire obligerait l'opérateur qui découvre une faute de saisie au moment d'imprimer à ressortir de toutes les étapes pour y revenir, or corriger est ce qu'il fait le plus (§ 10.2).
Deux verrous existent pourtant, et ils sont ailleurs. L'état bloqué refuse toute modification, import compris (§ 9, § 10.1) ; le mode lecture, qui est le mode d'ouverture par défaut, refuse tout geste modifiant (§ 8.4). Aucun des deux ne dépend de l'étape atteinte.
Trois garanties valent pour tout geste qui modifie — les étapes 2 à 7, 9, 11, et la rétention de l'étape 10 — et ne sont pas répétées ensuite :
- le geste achevé est une entrée d'historique et un état sur le disque, jamais l'un sans l'autre (§ 8.2) ;
- il se reprend par un retour à un instant, et le journal ne se tronque jamais (§ 8.3) ;
- s'il est refusé, le refus nomme sa cause et le geste qui la lève, là où le geste a eu lieu : sur la place pour un geste du plan (§ 5.9), dans l'aperçu d'import pour une ligne de CSV (§ 10.1), sur le champ pour une saisie. Seul un geste qui détruit de la saisie ouvre une fenêtre à confirmer.
Les étapes 8 et 10 — lire le diagnostic, comparer — ne modifient rien et restent vivantes en lecture (§ 8.4).
1. Ouvrir ou créer l'événement. Obligatoire. L'opérateur choisit dans la liste (§ 8.1), crée, ou charge une démonstration (§ 15). Ouvrir ne réécrit jamais le fichier d'état (§ 8.4) ; créer et charger une démonstration écrivent, puisqu'ils produisent un événement neuf.
1b. Passer en écriture. Obligatoire avant toute modification, reprenable. Le § 8.4 ouvre tout événement en lecture : les étapes 2 à 7, 9 et 11 sont refusées tant que la commande « Modifier » n'a pas été exécutée. Elle est à place fixe, et le logiciel retourne seul en lecture après inactivité. Ce retour automatique tombe entre deux étapes et jamais pendant un geste, et le reprendre ne coûte rien puisque tout geste achevé est déjà sur le disque.
2. Importer la liste des participants. Facultative, reprenable. Le logiciel montre un aperçu avant tout import (§ 10.1), importe les lignes valides et réexporte les refusées, et compte l'import entier comme une seule entrée d'historique. Un second import met à jour sans effacer les champs absents, ou remplace en annonçant d'abord ce qu'il détruit. L'import est refusé en état bloqué, le message nommant la commande qui déverrouille.
3. Compléter la liste à la main. Facultative, reprenable, y compris après la génération. Le logiciel n'exige que le nom (§ 10.2) et n'invente aucune valeur manquante.
4. Préparer les tables. Obligatoire. L'opérateur fixe le nombre de sièges par défaut, crée les tables, surcharge celles qui diffèrent (§ 6.1) et les dispose sur le plan (§ 7.1). Le logiciel énonce la conséquence chiffrée d'un changement de défaut avant de l'appliquer, n'écrase aucune surcharge, et refuse en bloc une baisse qu'une seule table refuse (§ 6.1).
5. Fixer le nombre de tours. Obligatoire. Retirer un tour puis le remettre retrouve les ancrages intacts, parce que la portée « tous les tours » n'est jamais développée (§ 4.3). La protection s'arrête là, et le logiciel le dit avant d'appliquer : retirer le tour r détruit les réservations de portée « tour désigné » portant sur r, et les tours au-delà de r des placements déjà engendrés. Les annoncer après coup ferait découvrir la perte à l'impression.
6. Désigner les animateurs, table par table. Facultative — une soirée sans aucun animateur est une configuration valide, et la petite démonstration en est une (§ 15.3). Reprenable : une désignation se retire, se déplace, s'ajoute après la génération.
L'opérateur désigne lui-même quelle personne anime quelle table. Il n'existe aucune commande qui répartit les animateurs sur les tables (§ 5.8).
Un geste de désignation produit deux faits sur la même place, sous une seule entrée d'historique : une réservation de portée « tous les tours » au nom de la personne, et un titre de place (§ 4). Les deux ensemble font l'animateur ; ni l'un ni l'autre seul. Le titre reste quand son titulaire part, et la table affiche « titre animateur non pourvu » (§ 4.1). Le statut d'« ancré » du § 4.2 en découle comme un fait calculé : le logiciel ne porte aucune case « est animateur ».
« La place » se lit de deux façons, et le logiciel tranche : c'est un siège quand l'attribution des sièges est active (§ 5.3), la table sinon. Dans le second cas, une table porte un compte de titres, pourvus ou non — trois animateurs à une table sans sièges attribués, c'est trois titres sur la table, dont deux peuvent rester non pourvus. Sans cette lecture, le refus « un siège inexistant » du § 5.9 n'a pas de sens quand les sièges ne sont pas attribués, et la liste des titres non pourvus ne sait pas compter.
Zéro, un, ou plusieurs animateurs par table. Le logiciel n'impose ni
plancher ni plafond propre à ce rôle. Des trois contrôles de capacité du § 5.9,
un seul garde cette étape : k_t > c_t est refusé — pas plus d'animateurs
qu'une table n'a de sièges. k_t = c_t est accepté et signalé : la table ne
tourne plus. Σ (c_t − k_t) < n ne se déclenche jamais ici, l'écart étant
invariant sous toute réservation (§ 5.9). Les autres refus du § 5.9 gardent
cette étape comme n'importe quelle autre pose : personne exclue, deux places sur
un même tour, dépôt sur une place déjà réservée à un autre.
Le logiciel n'assigne pas, il montre le travail qui reste, par deux listes cliquables qui mènent à la place ou à la personne : la liste des titres de place non pourvus (§ 4.1), et la liste des participants portant un titre pressenti (§ 4) qui ne sont encore réservés nulle part.
7. Placer des participants à la main. Facultative, reprenable. Ce sont des réservations ordinaires (§ 5.8). Le logiciel refuse de déposer quelqu'un sur une place réservée à un autre ; déplacer une réservation est une commande séparée (§ 5.9).
8. Lire le diagnostic. Le logiciel le présente, l'opérateur n'a rien à demander. Avant toute recherche, il énonce ce que la configuration rend inévitable, combien, et ce qui le rendrait évitable (§ 5.6), donne le plafond a priori (§ 5.5), et donne le plancher de l'écart d'itinéraire maximal quand la configuration en impose un. Sans ce dernier, le troisième chiffre de la page de qualité envoie relancer vers un zéro que la configuration interdit.
9. Générer. Nécessaire pour obtenir un placement, reprenable sans perte. La commande prend une graine et un compte d'arrêt ; chaque proposition conserve sa graine dérivée, ce compte et la longueur d'historique qui l'ont produite (§ 5.7, § 8.9). Le logiciel produit plusieurs propositions comparables ; relancer ajoute des candidats, un classement unique les ordonne tous. Les réservations des étapes 6 et 7 sont honorées, le moteur place les autres autour (§ 5.2). Le logiciel nomme automatiquement l'instant qui précède chaque génération (§ 8.3).
10. Comparer, et retenir. Facultative en droit — l'impression d'un plan non retenu sort en filigrane BROUILLON (§ 11.7) — nécessaire en fait pour distribuer quoi que ce soit. L'opérateur lit la page de qualité (§ 12), compare deux propositions sur le même axe — celui du manque (§ 12.10) —, retient celle qu'il préfère. Le logiciel sépare ce que le moteur a décidé de ce que les réservations imposent (§ 5.4). Retenir fait passer le plan à l'état Retenu (§ 9) ; les propositions non retenues restent disponibles.
11. Ajuster à la main. Facultative, reprenable. Le même geste qu'à l'étape 7 déplace maintenant une personne réelle dans le placement retenu. Le geste est identique, l'état des données change, et l'interface le dit (§ 5.8). Les indicateurs et la page de qualité suivent l'ajustement (§ 12.9 : la page lit, elle ne recalcule rien).
12. Imprimer. La planche de badges (§ 11.1), qui est le seul document PDF, et au besoin les vues imprimables : liste d'accueil, feuille par table, plan, rapport, page de qualité (§ 11.7). Le logiciel n'interdit pas d'imprimer un plan qui n'est ni retenu ni bloqué ; il le marque BROUILLON en filigrane. Produire la planche n'est pas une modification : elle reste disponible en mode lecture et sur un plan bloqué. L'opérateur bloque ensuite le plan pour le distribuer (§ 9).
Ce qui doit avoir eu lieu. Les étapes 2, 3, 5, 6, 7, 10 et 11 sont facultatives ; 2, 3, 4, 5, 6, 7, 9 et 11 se reprennent indéfiniment, dans n'importe quel ordre. Deux dépendances seulement : la 4 et au moins l'une des étapes 2 ou 3 avant la 9 — le générateur refuse une configuration à zéro participant ou à zéro table (§ 15.4) — et la 10 avant la 12 pour qu'une impression ne sorte pas en brouillon.
3. Périmètre
Dans le périmètre. Saisir les participants, décrire la salle, générer et comparer des placements, retoucher à la main, mesurer la qualité, imprimer ce qu'il faut pour tenir la soirée.
Hors périmètre, explicitement. Les inscriptions en ligne, la billetterie, le paiement, l'envoi de courriels, le travail à plusieurs sur un même plan, la synchronisation entre postes, les comptes et les droits d'accès.
Un seul opérateur, une seule machine, un seul événement ouvert à la fois.
4. Les objets manipulés
Événement — un nom, une date, un nombre de sièges par défaut, et tout le reste ci-dessous. Le logiciel en gère plusieurs (§ 8).
Participant — un identifiant entier court, unique dans l'événement et jamais réutilisé ; un nom ; éventuellement un prénom, un courriel, des notes, et une appartenance — l'étiquette qui sépare les gens qui se connaissent déjà : entreprise, équipe, famille, école.
L'identifiant est un petit entier et non un identifiant opaque. Le calcul de taille du § 8.6 repose dessus : sous la forme positionnelle du § 8.9, un identifiant de trente-six caractères remplace quatre octets par trente-neuf et décuple le poids des placements, qui sont la partie volumineuse du fichier.
Un participant porte aussi un titre pressenti — « animatrice », « hôte » — qui est purement informatif : il s'imprime sur sa fiche, il n'attribue aucun rôle, il ne pourvoit aucune place et il ne contraint rien. Il existe pour qu'une liste reçue où les animateurs sont signalés ne perde pas cette information à l'import.
Un participant peut être exclu sans être supprimé : il reste dans la liste, il n'est plus placé, et il sort de toutes les statistiques.
Table — un identifiant entier, unique dans l'événement et jamais réutilisé, exactement comme un participant ; un numéro affiché à partir de 1 ; un nombre de sièges ; une forme (ronde ou carrée) ; une position sur le plan en centimètres. Le nombre de sièges d'une table ne change pas d'un tour à l'autre : c'est une propriété du mobilier.
L'identifiant et le numéro affiché sont deux choses. La forme positionnelle du § 8.9 indexe les placements par identifiant : indexée par le rang d'une table, elle se tromperait de table dès qu'une table est supprimée, et le ferait en silence — trente-trois listes glissent d'un cran et tout le monde change de salle.
Tour — une des R rondes de la soirée. À chaque tour, chaque participant non exclu est assis à exactement une table.
Placement — pour chaque tour, qui est à quelle table, et à quel siège si les sièges sont attribués.
Réservation — le seul objet qui fixe quelqu'un quelque part. Elle lie une personne à une place, et porte une portée : un tour désigné, ou tous les tours. Elle contraint la génération, qui l'honore, et elle lui survit.
Titre de place — un libellé attaché à une place, jamais à une personne : « ce siège est le siège de l'animateur ». Il ne nomme personne et vaut pour toute la soirée.
4.1 Pourquoi le titre appartient à la place
Si le titre voyageait avec la personne, retirer celle-ci du plan emporterait le rôle sans bruit : la table se retrouverait sans animateur et rien ne le dirait avant le soir même. Attaché au siège, le titre reste quand son titulaire part, et le logiciel affiche « table 3 : titre animateur non pourvu ».
Un titre n'est pourvu que par une réservation. Quelqu'un que le moteur assoit sur une place titrée n'en devient pas titulaire : la place reste comptée « non pourvue ». Dériver le rôle de la simple occupation ferait désigner l'animateur par un tirage au sort, changeant à chaque relance.
Les trois combinaisons se produisent dans une préparation ordinaire :
| cas | ce que l'opérateur fait |
|---|---|
| titre sans réservation | « il faut un animateur à la table 5, on ne sait pas encore qui » — titre non pourvu, et la liste de ces titres, dite liste des titres de place non pourvus, est sa liste de travail (§ 2.1, étape 6) |
| réservation sans titre | asseoir quelqu'un près de la sortie parce qu'il part tôt |
| les deux | le cas courant de l'animateur |
4.2 Trois statuts, calculés et non déclarés
| statut | définition |
|---|---|
| ancré | une réservation fixe la même table à chacun des R tours |
| mobile | aucune réservation |
| partiellement fixé | le reste : quelques tours, ou des tables différentes |
Le statut se dérive du fait, jamais d'une case à cocher ni de la forme du stockage : qu'il résulte d'une portée « tous les tours » ou de R réservations de tour désigné sur la même table, le participant est ancré. Une case « est animateur » posée à côté d'une réservation absente peut mentir ; un statut calculé ne le peut pas.
Le logiciel propose de convertir R réservations de tour désigné en une portée « tous les tours », et ne le fait pas seul : la conversion change ce qui arrive quand un tour est ajouté.
4.3 La portée « tous les tours » est symbolique
Elle est stockée telle quelle, jamais développée en R réservations. La conséquence tranche un cas limite : retirer le tour 4 puis le remettre retrouve l'ancrage intact, parce qu'il n'a jamais été énuméré. Une portée matérialisée obligerait à choisir entre détruire les lignes excédentaires — l'opérateur ne retrouve pas son travail — et les garder en sommeil, c'est-à-dire porter un état invisible.
4.4 L'exclusion l'emporte
Exclure une personne suspend toutes ses réservations, rend ses places aux mobiles, rend les titres correspondants non pourvus, et le logiciel le dit. La réintégration les rend. L'exclusion décrit une absence physique : aucune table ne peut asseoir quelqu'un qui n'est pas là.
5. Le cœur : le placement
5.1 Le problème
Étant donné N participants, T tables de capacités c₁…c_T et R tours, répartir les gens à chaque tour de façon à maximiser le nombre de personnes distinctes que chacun rencontre, sous les contraintes que l'opérateur active.
5.2 L'instance réduite : ce que les réservations changent
Soit k le nombre d'ancrés, dont k_t à la table t. Le moteur résout une instance réduite : n = N − k mobiles, des tables de capacité libre c′_t = c_t − k_t, les mêmes R tours.
C'est la forme du problème initial à une donnée près, et elle n'est pas facultative : chaque table transporte l'ensemble des appartenances de ses ancrés. Un mobile d'appartenance X placé à une table où siège un ancré d'appartenance X viole « séparer les appartenances » à chacun des R tours. Réduire sans cette donnée produit un plan qui assoit un invité à côté d'un collègue animateur tous les tours, sans qu'aucun indicateur ne s'en aperçoive.
Moyennant cet ajout, le moteur n'a qu'un seul chemin de code : la réservation est un prétraitement qui réduit les capacités, retire des participants et annote les tables — jamais un second algorithme.
5.3 Les contraintes, activables une à une
| contrainte | ce qu'elle exige |
|---|---|
| Séparer les appartenances | deux personnes de la même appartenance ne partagent pas une table |
| Nouveaux voisins à chaque tour | deux personnes ne se retrouvent pas deux fois à la même table |
| Nouvelle table à chaque tour | voir ci-dessous |
| Varier les appartenances rencontrées | sur l'ensemble des tours, chacun rencontre le plus d'appartenances différentes possible |
| Attribuer les sièges | chacun reçoit un numéro de siège, pas seulement une table |
Ce sont des objectifs, pas des interdits : quand la configuration les rend impossibles à satisfaire toutes, le moteur produit le meilleur compromis et dit lequel, au lieu de refuser.
« Nouvelle table à chaque tour », énoncée correctement. La formulation naïve — « personne ne revient à une table déjà occupée » — est fausse pour un ancré dès le deuxième tour, et une exception « sauf les animateurs » laisserait sans réponse le cas d'un participant ordinaire réservé au tour 1. L'énoncé porte donc sur ce que le moteur décide :
Le moteur n'assoit jamais un participant à une table où il l'a déjà assis, ni à une table où l'opérateur l'a placé.
Aucune exception n'est nécessaire. Pour un ancré, le moteur ne décide rien : la règle est vraie sans rien exiger.
« Varier les appartenances » n'est pas « séparer les appartenances ». La première porte sur celle des autres, à travers les tours ; la seconde sur la sienne, à l'intérieur d'un tour. Elles s'activent séparément.
5.4 Les indicateurs
Tout placement proposé porte les mêmes chiffres, pour que la comparaison soit une lecture et non un jugement :
| indicateur | définition |
|---|---|
| personnes distinctes rencontrées | sur l'ensemble des tours ; une personne revue compte une fois |
| collisions cumulées | occurrences (paire, tour) où deux personnes d'une même appartenance partagent une table — l'unité dans laquelle les planchers du § 5.6 se démontrent |
| paires de même appartenance | paires distinctes réunies au moins une fois ; jamais la même unité que la ligne précédente |
| excédent de collisions | collisions cumulées − paires distinctes : le nombre de retrouvailles au-delà de la première entre deux personnes d'une même appartenance. Nul quand aucune paire de collègues n'est réunie deux fois |
| rencontres répétées | paires réunies plus d'une fois |
| nombre maximal de rencontres d'une même paire | |
| retours à une table déjà occupée | voir ci-dessous |
| appartenances distinctes rencontrées — A(p) | nombre d'appartenances différentes portées par les personnes affiliées que p rencontre, la sienne comprise |
| redondance d'appartenance — r(p) | |F(p)| − A(p), où F(p) sont les affiliés rencontrés ; r = 0 signifie qu'aucune appartenance n'a été recroisée |
| taux de diversité — d(p) | A(p) / |F(p)| ; « — » quand |F(p)| = 0 |
| manque | plafond réalisé − rencontres (§ 5.5) — la grandeur qui compare deux propositions (§ 12.10) |
| écart d'itinéraire | plafond a priori − plafond réalisé (§ 5.5, § 12.10) |
Les agrégats portent sur tous les participants placés. Le minimum et la moyenne se calculent sur l'ensemble, ancrés compris, et deux lignes secondaires donnent la même mesure restreinte aux mobiles et aux ancrés. Un participant partiellement fixé compte parmi les mobiles.
Un ancré n'est pas structurellement désavantagé : à table de 8 avec un seul ancré et R = 4, son plafond vaut 28, exactement celui d'un mobile visitant quatre tables de 8. Le retirer du minimum cacherait un animateur réellement mal servi, qui est précisément la personne dont on veut connaître le sort. Les deux lignes secondaires existent pour que l'opérateur sache si le minimum global désigne un défaut qu'une relance corrige, ou la conséquence de son propre plan de réservation.
Le minimum compte plus que la moyenne. Il décrit le sort de la personne la plus mal servie, qui est celle qui se plaindra.
Les deux unités de collision ne se mélangent jamais. Un chiffre de collision cité quelque part — § 5.6, § 12.4, § 15.3, une étape du pilotage — nomme son unité. Un plan qui garde les deux mêmes collègues ensemble aux quatre tours mesure 4 collisions cumulées et 1 paire distincte : confondre les deux fait afficher une mesure inférieure à son propre plancher.
Une collision répétée coûte plus cher que la même quantité répartie, et c'est l'excédent qui le dit. Trois plans à comparer, tous en conflit :
| plan | collisions cumulées | paires distinctes | excédent |
|---|---|---|---|
| un collègue retrouvé quatre fois | 4 | 1 | 3 |
| deux collègues retrouvés deux fois chacun | 4 | 2 | 2 |
| six collègues retrouvés une fois chacun | 6 | 6 | 0 |
Le dernier est le moins mauvais, bien qu'il porte le plus de collisions cumulées — passer la soirée avec le même collègue est pire que croiser six collègues une fois. Ni les collisions cumulées ni les paires distinctes, prises seules, ne rendent cet ordre : les deux classent le premier plan en tête, c'est-à-dire exactement à l'envers. L'excédent le rend, et c'est pour cela qu'il ouvre le classement du § 5.7.
Les trois restent affichés : ce sont trois conflits, et aucun ne se cache derrière la grandeur qui ordonne.
Quand une population est vide, le logiciel écrit « — », jamais zéro. Un ensemble vide n'est pas une mesure nulle.
Ce que le moteur a décidé, et ce que les réservations imposent. L'indicateur « retours à une table » ne compte que les retours choisis par le moteur ; ce que les réservations imposent est énoncé à part et chiffré — « 99 retours imposés par 33 ancrages ». Sinon l'indicateur affiche la même valeur pour toute proposition et cesse de les séparer. La même lecture vaut pour les rencontres répétées entre deux personnes réservées à la même table.
Les indicateurs se mesurent sur le plan complet, les N participants et les R tours, comme si les réservations n'existaient pas. Des formes closes servent au diagnostic préalable ; elles ne corrigent jamais après coup un chiffre mesuré. Deux arithmétiques pour une même quantité sont le mode de défaillance que le § 13.2 interdit.
5.5 Le plafond et la borne prouvée
Deux notions, deux noms, jamais trois.
Le plafond est une borne arithmétique, individuelle, toujours calculable. La borne prouvée est l'affirmation plus forte qu'une construction l'atteint. Elle seule autorise la mention « minimum atteint ».
Une seule formule, paramétrée par l'itinéraire. Écrire une formule par statut garantit qu'elles se contrediront, et aucune ne couvrirait un participant partiellement fixé. Pour une personne p et un itinéraire — la table qu'elle occupe à chaque tour :
- D = les tables distinctes visitées, m_t = tours passés à la table t ;
- a_t = le nombre d'ancrés de la table t, p non compté ;
- v_t = c_t − 1 − a_t = les sièges qui changent d'occupant ;
- n_p = n − 1 si p est mobile ou partiellement fixé, n s'il est ancré.
plafond(p) = min( N − 1 , Σ a_t + min( n_p , Σ m_t · v_t ) )
t∈D t∈D
Les ancrés d'une table visitée sont rencontrés une fois, quel que soit le nombre de tours passés là ; les sièges libres se renouvellent, mais ne peuvent livrer plus de personnes qu'il n'existe de mobiles.
Le terme
min(n_p, …)n'est pas décoratif. Un ancré ne rencontre jamais les ancrés des autres tables. Sans ce terme, sur 4 tables de 5 avec un ancré chacune et 5 tours, la formule annonce 19 là où 16 est le vrai maximum : l'opérateur voit à perpétuité un animateur « à 84 % de son plafond » et relance une génération qui ne peut rien.
Deux moments, deux bases, et un glossaire qui fait autorité. Le plafond dépend de l'itinéraire, et les deux plafonds ne se calculent pas sur la même base :
| terme | définition | base | où il s'affiche |
|---|---|---|---|
| plafond a priori | maximisé sur les itinéraires admissibles | capacités | diagnostic § 5.6, tuile § 12.2 |
| plafond réalisé | calculé sur l'itinéraire de la proposition | occupation | à côté de chaque mesure |
| borne prouvée | l'affirmation qu'une construction atteint le plafond | — | mention « minimum atteint » |
| manque | plafond réalisé − rencontres | — | tuile 2, courbe de comparaison (§ 12.10) |
| écart d'itinéraire | plafond a priori − plafond réalisé | — | tuile 3, avec son plancher |
| écart au plafond a priori | manque + écart d'itinéraire | — | départage § 5.7, certificat |
Ces six lignes sont la seule définition faisant autorité, et l'expression « l'écart », employée seule, ne paraît nulle part : trois quantités affichées sous un même libellé font conclure à l'opérateur que le logiciel compte mal. La règle « deux notions, deux noms, jamais trois » n'est pas affaiblie — aucun de ces noms ne désigne une borne, et il n'existe toujours que deux bornes ; elle est simplement tenue par un tableau plutôt que par la mémoire du rédacteur.
Sans le partage a priori / réalisé, le diagnostic annonce 28, la page de qualité affiche 27, et l'opérateur conclut à une régression du moteur.
Le plafond réalisé se calcule sur l'occupation, jamais sur la capacité. Soit I l'itinéraire de p — les couples (table, tour) où p est assis —, D les tables distinctes de I, et o_{t,r} l'occupation de la table t au tour r :
plafond réalisé(p) = min( N − 1 , Σ a_t + min( n_p , Σ v_{t,r} ) )
t∈D (t,r)∈I
avec v_{t,r} = o_{t,r} − 1 − a_t
Le terme des ancrés reste une somme sur les tables distinctes — un ancré rencontré à trois tours compte une fois ; seul le terme des sièges qui se renouvellent devient une somme sur les tours parcourus. Sans cette substitution, une personne assise à une table de 8 occupée par 7 porte un manque de 1 qu'aucun placement ne retire, et le manque perd la seule propriété qui le rend lisible : zéro veut dire rien à reprendre. Les plafonds annoncés au § 15.1 ne changent pas, la configuration principale étant exactement tendue — l'occupation y vaut la capacité à chaque tour.
L'inégalité plafond a priori ≥ plafond réalisé n'est vraie que dans cet
ordre : pour un même itinéraire, l'occupation ne dépasse jamais la capacité.
Deux chemins seulement concluent « minimum atteint ».
- Par certificat — une proposition égale le plafond. Ce chemin ne suppose rien.
- Par famille connue — l'instance appartient à une famille dont la construction parfaite est démontrée. Les conditions portent sur l'instance réduite, pas sur l'instance complète, et les deux sens se produisent.
Un réduit parfait ne prouve pas le plafond complet, et c'est vérifié par énumération : 16 mobiles, 4 tables de 5 dont une place ancrée chacune, 5 tours. Le réduit — 16 points, blocs de 4 — est un plan affine d'ordre 4, chaque mobile rencontre bien les 15 autres. Son plafond vaut pourtant 19 et n'est pas atteint : aucune affectation des blocs aux tables ne donne plus de 2 tables distinctes au mobile le plus mal servi, soit 17 sur 19. La construction porte sur le réduit ; le plafond sur le complet ; l'affectation des blocs aux tables est un troisième objet, et c'est lui qui décide.
Le certificat se lit contre le plafond a priori, donc sur manque + écart d'itinéraire, jamais sur le seul manque : une proposition qui range tout le
monde sur des itinéraires bas atteint son plafond réalisé partout et n'a rien
prouvé. Lire le certificat sur le manque seul ferait annoncer « minimum atteint »
à la proposition qui a le plus mal routé les gens. Conséquence directe, que le
guide d'usage écrit : la grande démonstration ne décerne jamais ce
certificat, son écart d'itinéraire maximal étant plancher à 1 (§ 12.10.4).
Quand aucun chemin ne conclut, le logiciel affiche l'écart au plafond a priori et écrit « atteignabilité inconnue », jamais « peut mieux faire » : l'un invite à relancer, l'autre dit que relancer ne prouvera rien.
La borne de diversité. Soit G le nombre d'appartenances déclarées et g_p l'effectif de celle de p :
A_max(p) = min( Φ(p) , G , (N − g_p) + 1 )
où Φ(p) est le plafond recalculé en ne comptant comme occupants que les affiliés. Le « + 1 » compte sa propre appartenance, qu'un collègue rencontré suffit à apporter : sans lui, un événement où tout le monde partage une appartenance annoncerait A_max = 0 alors que chacun en rencontre une.
5.6 Le diagnostic, avant la recherche
Certaines configurations rendent un conflit inévitable, quel que soit l'algorithme. Le logiciel les détecte avant de chercher et énonce ce qui est inévitable, combien, et ce qui le rendrait évitable :
- plus de personnes d'une appartenance que de tables → « au moins 8 collisions cumulées sur les 4 tours, portées par au moins 2 paires distinctes ». Le plancher se démontre en occurrences ; le chiffre nomme donc son unité, et le compte de paires distinctes l'accompagne (§ 5.4) ;
- le plancher de l'excédent, quand un groupe de g personnes subit un plancher
de C collisions cumulées et que
C > C(g, 2): les retrouvailles sont alors forcées, et l'excédent ne descend pas sousC − C(g, 2). Le diagnostic l'écrit quand il est strictement positif, et se tait sinon — annoncer un plancher nul laisserait croire qu'un plancher a été calculé là où il n'y en a pas ; - redondance minimale
r_min(p) = max(0, |F(p)| − G)→ « au moins 6 appartenances recroisées par personne ; avec 3 tours au lieu de 4, aucune ne le serait » ; - tous les animateurs de même appartenance → chaque mobile accumule au moins min(R, T) − 1 redondances qu'aucune recherche ne réduit. Remède : les laisser sans appartenance déclarée, ou leur en donner des distinctes ;
- taux d'ancrage par table : une table dont tous les sièges sont ancrés ne tourne plus ; une table à moitié ancrée réduit d'autant le brassage de tout l'événement ;
- le plancher de l'écart d'itinéraire maximal, quand la configuration en impose un (§ 12.10.4). Le diagnostic ne l'écrit que là où il sait le prouver, et se tait ailleurs — jamais 0 par défaut. Sans lui, le troisième chiffre de la page de qualité envoie relancer indéfiniment vers un zéro que la configuration interdit ;
- le manque de places,
Σ (c_t − k_t) < n. Ce n'est pas un refus de geste mais une propriété de la configuration (§ 5.9) : le diagnostic la nomme avec le nombre de places manquantes, et la génération la refuse.
Un opérateur à qui l'on dit « c'est impossible, voici pourquoi, voici le remède » cesse de relancer en espérant mieux.
5.7 Plusieurs propositions, pas une
Une génération produit plusieurs placements distincts et comparables, en nombre réglable. Les générations s'accumulent : relancer avec d'autres réglages ajoute des candidats plutôt que d'effacer les précédents, et un classement unique les ordonne tous. Une commande efface la liste en épargnant celui qui est retenu.
La commande prend une graine entière et un compte d'arrêt, et une longueur d'historique d'acceptation dont l'algorithme fixe la valeur par défaut. Chaque proposition conserve sa graine dérivée, ce compte et cette longueur (§ 8.9). C'est le support de « à graine et entrée égales, placement identique » ; sans eux, une proposition retenue cesse d'être régénérable.
L'identifiant entier d'une proposition vient de sa place dans la suite de graines dérivées, jamais de son ordre d'achèvement. Quand plusieurs propositions se calculent en parallèle, leur ordre d'arrivée varie : un identifiant attribué à l'achèvement ferait départager deux propositions à égalité par la vitesse de la machine, et deux exécutions les classeraient dans deux ordres.
La recherche descend un scalaire ; le classement affiché est un ordre explicite, écrit dans l'interface. Confondre les deux ferait dépendre l'ordre de poids internes que l'opérateur ne voit pas.
Ordre par défaut : écart au plafond a priori le plus faible (§ 5.5), puis excédent de collisions, puis collisions cumulées, puis rencontres répétées, puis redondance d'appartenance comme départage.
Le premier rang se justifie par l'asymétrie des gains — rencontrer une personne de plus est un gain certain, rencontrer une appartenance de plus est un raffinement. Mettre la diversité au-dessus ferait refuser un contact neuf parce que l'appartenance du voisin est déjà croisée.
Les deux rangs de collision sont dans cet ordre, et l'inverse serait faux. Le § 5.4 le démontre en trois plans : classer d'abord sur les collisions cumulées — comme classer d'abord sur les paires distinctes — met en tête le plan qui fait passer toute la soirée aux deux mêmes collègues, parce qu'il en porte le moins. L'excédent d'abord, le volume ensuite : un plan qui répartit six collisions sur six paires passe devant un plan qui en concentre quatre sur une seule.
Les paires distinctes ne sont pas une clé de tri ; elles restent une colonne affichée, et se retrouvent de toute façon par soustraction. Trois clés pour deux quantités indépendantes feraient un départage qui ne départage rien.
Quand le plafond a priori par personne n'est pas disponible (§ 17, point 4), le premier critère est sauté et la page nomme celui qu'elle applique à sa place : un classement qui se tait sur le critère qu'il n'a pas pu appliquer se lit comme un classement complet.
Un départage ne se déclenchant presque jamais, les chiffres de diversité sont affichés en colonne pour chaque proposition, et non pour la seule gagnante.
5.8 Le placement à la main
L'opérateur peut asseoir des gens avant toute génération : ce sont des réservations. La génération les honore et place les autres autour. Une commande transforme un placement partiel en placement complet, le générateur comblant les vides.
Une fois un placement retenu, le même geste déplace une personne réelle. Le geste est identique ; ce qui change est l'état des données, et l'interface le dit, pour que l'opérateur sache lequel des deux il fait.
Il n'existe aucune commande qui répartit les animateurs sur les tables. L'opérateur désigne lui-même quelle personne anime quelle table (§ 2.1, étape 6). Une commande qui attribuerait un animateur par numéro de table le ferait sous une seule entrée d'historique : revenir sur elle, parce que douze attributions sur trente-trois sont fausses, emporterait aussi les vingt-et-une qui étaient bonnes — le § 8.3 restitue un état, il ne défait pas un geste.
Ce qui la remplace : le logiciel montre le travail qui reste, par deux listes cliquables qui mènent à la place ou à la personne — la liste des titres de place non pourvus (§ 4.1) et la liste des participants portant un titre pressenti (§ 4) qui ne sont encore réservés nulle part.
5.9 Ce que le logiciel refuse, au moment du geste
Ces contrôles s'exécutent à la pose, jamais à la génération : un échec de génération que rien n'explique coûte une relance inutile et la confiance mise dans l'outil.
- k_t > c_t — plus d'ancrés que de sièges : refusé.
- k_t = c_t — table entièrement gelée : accepté, signalé, parce que c'est rarement l'intention.
- Σ (c_t − k_t) < n — il manque des places à chaque tour. Celui-ci n'est
pas un refus de geste. L'écart
Σ (c_t − k_t) − nest invariant sous toute réservation : une portée « tous les tours » retire la personne des mobiles et son siège de la capacité libre en parts égales ; une portée « tour désigné » rend la personne partiellement fixée, qui compte parmi les mobiles (§ 5.4) et ne change ninnik_t; déplacer une réservation décrémente unk_tet en incrémente un autre. Aucun geste de réservation ne peut donc le faire franchir. C'est une propriété de la configuration : le § 5.6 l'évalue au diagnostic et la génération la refuse, en nommant le nombre de places manquantes. Les gestes qui la font franchir sont ceux qui augmententnou diminuentΣ c_t— un import, une saisie, la réintégration d'un exclu (§ 4.4), une baisse de capacité (§ 6.2), la suppression d'une table — et aucun n'est refusé pour cette raison : refuser un import parce que la salle est devenue trop petite empêcherait d'enregistrer qui s'est présenté, et le § 9 pose déjà que la dérive avertit sans bloquer. Chacun avertit, avec le nombre. - Réserver plus de places qu'une table n'en a ; un siège inexistant ; la même personne à deux places sur un même tour ; une personne exclue.
- Déposer quelqu'un sur une place réservée à un autre, ou l'échanger avec elle. Déplacer la réservation est une commande séparée et explicite.
Un refus nomme sa cause et propose le geste qui le lève, et se manifeste sur la place concernée, pas dans une boîte de dialogue. Seul un geste qui détruit de la saisie ouvre une fenêtre à confirmer : une suite de modales apprend à cliquer sans lire, et le premier refus qui comptait passe inaperçu.
5.10 Le coût d'évaluation
A(p) et r(p) demandent de parcourir les personnes rencontrées par chacun. Recalculés intégralement à chaque candidat d'une recherche locale, ils dominent le temps de génération. Le moteur les met à jour de façon incrémentale, sur les seules tables modifiées. C'est une exigence de faisabilité : sans elle, l'objectif existe dans l'interface et ne converge pas dans le temps que l'opérateur accorde.
La recherche s'arrête sur un compte, jamais sur une durée — un nombre
d'itérations, ou un nombre de mouvements consécutifs sans amélioration — fixé
dans les réglages de génération et enregistré avec la proposition, à côté de sa
graine (§ 8.9). Le temps que l'opérateur accorde s'achète par la mise à jour
incrémentale, pas par un chronomètre. Une recherche bornée par une durée rendrait
le placement dépendant de la vitesse de la machine : à graine et entrée égales,
deux exécutions donneraient deux plans, et toute la reproductibilité des captures
d'écran (§ 19.4) tomberait avec. L'interdit de performance.now dans le moteur
(§ 14.7) n'est donc pas une règle d'hygiène : c'est elle qui rend vraie la phrase
« à graine égale, placement identique ».
Le compte est un réglage, et son plafond d'usage est de trente minutes. Il est exposé dans les réglages de génération, avec son unité ; le logiciel affiche à côté la durée réellement observée du dernier calcul sur cette machine, qui est la seule façon d'étalonner un compte. Le réglage livré par défaut est celui qui, sur la machine de référence, reste bien en deçà de ce plafond.
Une génération longue s'annonce avant de partir. À partir de la dernière durée mesurée, le logiciel énonce l'ordre de grandeur attendu — « environ vingt minutes à ce réglage » — et demande. Une recherche d'une demi-heure lancée par mégarde est une demi-heure de soirée perdue, et aucun réglage par défaut ne protège de cela.
Pendant la recherche, l'application reste vivante. Le calcul ne tient pas le fil d'exécution de l'interface : le plan reste lisible, la page de qualité des propositions déjà obtenues reste ouverte, et l'avancement est visible. Une demi-heure de fenêtre figée se lit comme un plantage, et l'opérateur tue le logiciel — ce qui perd exactement ce que la recherche avait trouvé. Le compteur d'inactivité du § 8.4 est suspendu tant qu'un calcul tourne, donc le retour automatique en lecture ne tombe jamais au milieu d'une génération.
Une commande annule une génération en cours, et elle écarte le résultat partiel. L'annulation est immédiate — elle n'attend pas la fin d'une itération longue. Une proposition issue d'une recherche tronquée porterait la même graine et le même compte qu'une proposition complète, et ne s'en distinguerait dans aucun fichier.
Le recalcul complet est la mesure, celui dont sortent les chiffres du § 5.4 et de la page de qualité ; l'incrémental n'existe que dans la boucle de recherche, où il sert de score et ne s'affiche jamais. C'est l'exception nommée au § 13.2. Sans ce partage, la recherche descend un score dérivé et converge vers un plan que les indicateurs affichés classeront sous un plan qu'elle a rejeté, et l'opérateur verra le classement contredire la proposition.
Le logiciel éprouve l'incrémental contre le recalcul complet sur chaque configuration de démonstration, à 1, 10, 100, 1 000 et 10 000 mouvements — non au seul terme : comparer à la fin laisse passer une dérive qui se compense, et ne dit pas à quel ordre de grandeur le défaut affleure, qui est précisément le chiffre que l'épreuve doit produire (§ 14.10).
6. Les sièges d'une table
6.1 Un défaut d'événement, des surcharges par table
L'événement porte un nombre de sièges par défaut. Toute table créée suit ce défaut ; elle n'en garde pas une copie. Une table peut recevoir une surcharge et cesse alors de le suivre.
| état d'une table | ce que cela veut dire |
|---|---|
| suit le défaut | sa capacité vaut le défaut et change avec lui |
| surchargée | sa capacité est fixée à la main, le défaut ne l'atteint plus |
Réaligner n'est pas un troisième état : c'est la commande qui retire une surcharge. L'interface montre l'état, pas seulement la valeur — une table à 8 qui suit un défaut de 8 et une table surchargée à 8 affichent le même nombre et réagissent différemment au prochain changement.
Changer le défaut n'écrase aucune surcharge. Une saisie manuelle table par table disparaîtrait sinon sans trace, et l'écart ne se découvrirait qu'à l'impression.
Le logiciel énonce la conséquence chiffrée avant de l'appliquer :
« 9 tables suivent le défaut et passent de 8 à 10 places ; 4 tables surchargées (7, 7, 6, 12) ne changent pas. Total : 104 → 122 sièges pour 118 participants. »
Deux boutons : appliquer, ou appliquer et réaligner les 4 surchargées — le second geste est séparé parce qu'il détruit de la saisie.
Un changement de défaut est une seule entrée d'historique, quel que soit le nombre de tables touchées. Baisser le défaut subit les refus du § 6.2, et le logiciel les évalue toutes avant d'en appliquer aucune : si une seule table refuse, l'opération entière est refusée, nommément, et rien ne change.
6.2 Ce que le logiciel refuse
- Moins de 2 sièges. Une table à 1 place est un participant qui ne rencontre personne de la soirée — c'est ce que le logiciel existe pour éviter.
- Une capacité inférieure au nombre de réservations portées par la table.
- Une réduction qui retire un siège portant une réservation ou un titre — les sièges disparaissent par numéro décroissant ; le refus les liste.
- Une capacité non entière, négative, ou saisie en texte.
Deux cas avertissent sans refuser : réduire sous le nombre de personnes assises (les surnuméraires vont dans la réserve visible, nommément listées, jamais effacées en silence) ; et dépasser le nombre de sièges au-delà duquel le dessin cesse d'être lisible — seuil mesuré au sens du § 14.1.
7. Le plan de salle
C'est la surface de travail, du début à la fin. Il est dessiné en SVG et l'opérateur agit directement dessus.
7.1 Les gestes
| geste | effet |
|---|---|
| cliquer une chaise | sélectionne la place — l'assigner, la réserver, lui donner un titre |
| glisser une personne sur un siège libre | la déplace |
| glisser une personne sur une autre | les échange |
| glisser une personne hors des tables | la retire du tour ; elle attend dans la réserve visible |
| glisser une table | change sa position |
| tirer la poignée d'une table | change son nombre de places |
| survoler une table en conflit | affiche le détail des règles violées |
| zoomer, déplacer la vue | change le cadrage |
Un menu par personne offre les mêmes actions sans glisser, pour le tactile et le clavier.
La poignée pilote le nombre de places, jamais une taille libre. Chaque table est dessinée à l'échelle de son nombre de sièges ; une taille libre permettrait un dessin qui ment sur la capacité, et ce mensonge n'apparaîtrait sur aucun indicateur. En tirant, l'opérateur fait défiler des nombres entiers ; les chaises apparaissent et disparaissent sous le pointeur.
La poignée refuse de descendre sous le nombre de sièges occupés, tous tours confondus — sinon un geste de géométrie désassoit deux personnes au troisième tour, que l'opérateur ne regarde pas. Le logiciel nomme le nombre bloquant et le tour concerné.
La rotation d'une table carrée n'existe pas dans la première itération : elle introduirait une orientation dans la numérotation des sièges et dans la position de la liste de noms. Le logiciel n'offre aucune poignée de rotation, plutôt qu'une poignée qui dérange la numérotation.
7.2 Les coordonnées
C'est l'endroit où un plan interactif se casse, silencieusement, et seulement quand on a zoomé.
Le <svg> racine ne porte pas de viewBox, ni bordure ni remplissage. Son
unité est donc le pixel CSS. La formule qui répartit la position du pointeur
dans la largeur d'un viewBox est fausse dès que les rapports diffèrent : le
moteur de rendu centre le dessin, laisse des bandes vides, et le plan répond
juste au centre et faux sur les bords. Supprimer la cause vaut mieux que
corriger l'effet.
L'état de vue est trois nombres : k, l'échelle en pixels par
centimètre, et tx, ty en pixels. Le plan vit dans un unique groupe
<g transform="translate(tx,ty) scale(k)"> : zoomer écrit un attribut sur un
élément.
La conversion est une fonction pure, donc éprouvée sous node :
dessin_depuis_ecran(vue, cadre, xEcran, yEcran) =
{ x: (xEcran − cadre.gauche − vue.tx) / vue.k,
y: (yEcran − cadre.haut − vue.ty) / vue.k }
Trois invariants la rendent vraie, et chacun est une épreuve : pas de
viewBox ; ni bordure ni remplissage sur le <svg> ; aucun ancêtre portant
une transformation CSS — elle déplacerait le rectangle sans déplacer les
unités, et le plan répondrait faux partout.
Une épreuve en navigateur confronte la fonction pure à
getScreenCTM().inverse(). Ce n'est pas une seconde implémentation : c'est un
oracle fourni par le moteur de rendu.
Le zoom se fait sous le curseur, sinon le contenu fuit vers un coin et l'opérateur poursuit la table qu'il visait.
Les bornes du zoom sont relatives, jamais absolues : k reste entre
0,5 × k_ajusté et 10 px/cm. Une borne basse absolue est une erreur
démontrable — sur un portable de 1 366 × 700, un plan de 36 m s'ajuste à
k ≈ 0,19 px/cm, et un plancher fixé à 0,2 rendrait l'ajustement à la fenêtre
impossible sur la configuration de démonstration elle-même.
Le plan vide — aucune table — n'a pas de rectangle englobant : k_ajusté
vaut 1 px/cm, l'origine est centrée. Sans cette règle, l'ajustement divise par
zéro et rend k = Infinity, que le transform accepte sans rien dessiner et
sans rien signaler.
Un glissement se calcule en écart, jamais en position absolue — sinon la table saute au premier mouvement pour mettre son origine sous le pointeur.
Le logiciel ne lit ni getBBox() ni getScreenCTM() dans un
pointermove : ces appels forcent un recalcul de mise en page au milieu de
la boucle d'images.
7.3 Le pointeur et le tactile
La capture du pointeur est obligatoire, et elle porte sur la racine
<svg>, pas sur la table. Sans capture, un mouvement rapide sort le curseur,
les événements vont à ce qui est dessous, le pointerup n'arrive jamais et la
table reste collée au curseur. Et si le composant remplaçait l'élément capturant
pendant le glissement, la capture serait perdue — raison pour laquelle le
logiciel écrit l'attribut transform directement pendant le glissement et
ne confie le résultat au modèle qu'au pointerup.
Pendant une capture, ev.target désigne l'élément capturant, pas ce qui est
sous le pointeur. Le logiciel ne cherche donc pas la cible d'un dépôt dans
le DOM : il convertit le point en coordonnées de dessin et demande au module
de géométrie quel siège le contient. Le dépôt devient une fonction pure,
éprouvable sous node, qui ne peut pas diverger du dessin puisque c'est elle
qui place les chaises.
pointercancel est une annulation, pas une fin : le logiciel remet la table
où elle était. La touche d'échappement et le passage en lecture font de même.
Le tactile. touch-action: none sur le plan, sans quoi le navigateur
interprète le glissement comme un défilement avant de livrer le moindre
pointermove — la panne tactile la plus fréquente, et la plus déroutante
puisqu'à la souris tout marche.
La taille des cibles dépend du zoom, et la règle doit le dire. Deux sièges
voisins d'une table de 8 sont à 60 cm ; une cible de 44 px exige
k ≥ 0,73 px/cm, le plancher de 24 px exige k ≥ 0,40 — tandis que
l'ajustement d'une salle entière donne k ≈ 0,2 à 0,45. Au cadrage le plus
utile, un siège n'est pas pointable au doigt. La règle est donc : sous le zoom
où la cible tomberait en deçà de 24 px, les sièges cessent d'être des cibles
et la table devient la cible, le siège se choisissant dans son panneau. Le
logiciel le montre en cessant de dessiner les chaises individuellement, plutôt
qu'en laissant l'opérateur manquer sa cible sans comprendre.
Il n'y a pas de survol au doigt : le détail d'un conflit est accessible par survol, par focus clavier et par pression longue — un seul composant d'infobulle, trois déclencheurs.
Un seul écouteur sur la racine, la cible étant identifiée par
closest('[data-siege]'). Les mouvements sont regroupés par image.
Le clavier n'enfile pas 264 arrêts de tabulation : le plan est un seul arrêt, les flèches passent d'une table à l'autre, l'entrée descend dans ses sièges, l'échappement remonte.
7.4 Le texte
<text> n'a aucun retour à la ligne : chaque ligne d'une liste est un
<tspan> à décalage vertical explicite. La largeur se mesure par
getComputedTextLength() sur la police rendue ; la hauteur de ligne se mesure
sur un gabarit portant capitales accentuées et jambages — « Ôjgq », « Æ »,
« W » — jamais une chaîne moyenne, jamais déduite de la taille de police.
Le logiciel n'emploie pas textLength : cet attribut comprime les glyphes
et produit un nom déformé plutôt qu'un nom tronqué.
La troncature se décide par document, pas par support — et sur le badge, par champ :
| document | règle |
|---|---|
| le plan de salle, à l'écran comme à l'impression | abrège avec une marque, la largeur d'un bloc de noms étant la même contrainte des deux côtés |
| la fiche à l'écran, la feuille par table, la liste d'accueil | ne tronquent jamais : elles replient |
| le badge (§ 11.5) | par champ : le nom ne s'abrège jamais — il réduit de corps par paliers, puis se replie — tandis que l'appartenance s'abrège sur une seule ligne |
Un nom coupé sur la feuille que tient le personnel de salle ne sert à personne ; un nom de deux cents caractères déployé sur un plan de 33 tables détruit la page.
7.5 Les tables en conflit
Une table dont le placement viole une règle active se signale par une classe posée à partir des indicateurs du moteur — le plan ne recalcule rien, il lit.
Le signal ne repose pas sur la seule couleur : contour épaissi, trame, et une pastille portant le nombre de violations. Une part notable des opérateurs ne distingue pas un rouge d'un vert.
Quand toutes les tables sont en conflit, le plan cesse de signaler quoi que ce soit — cas que le diagnostic sait prédire. La pastille chiffrée garde sa valeur puisqu'elle ordonne les tables entre elles, et le bandeau énonce le diagnostic plutôt que de laisser compter des contours rouges.
7.6 Le vocabulaire visuel des places
Aucun état ne se distingue par la seule couleur : chacun porte une forme. Les marques sont dessinées en SVG, jamais écrites avec des caractères emoji — leur présence et leur rendu dépendent des polices installées, ce qu'une livraison hors ligne ne contrôle pas.
| information | canal |
|---|---|
| réservée à une personne / titre seul / ni l'un ni l'autre | cadenas fermé / cadenas ouvert / aucune marque |
| pourvue / à pourvoir | remplissage plein / hachuré |
| portée « tous les tours » | anneau autour de la chaise |
La chaise porte au plus deux marques ; la liste porte le texte. Trois glyphes empilés sur une chaise deviennent illisibles au zoom par défaut. La liste numérotée énonce l'état en toutes lettres — « 1. (réservée, tous les tours) Nom — animateur » — et c'est elle qui fait foi.
7.7 L'échelle, et ce que le logiciel refuse d'affirmer
L'unité du modèle est le centimètre réel, dès le premier fichier. Le diamètre d'une table ronde se déduit de son nombre de places :
diamètre = max(70, places × 60 / π) en centimètres
Soit 115 cm pour 6 places, 153 pour 8, 191 pour 10. Le plancher n'est pas cosmétique : en deçà de six convives ce n'est plus le périmètre qui borne la table mais le plateau, et la formule seule dessinerait des guéridons. Les deux valeurs sont des réglages par défaut, non des normes vérifiées ; un diamètre saisi à la main l'emporte.
Les étiquettes ne sont pas à l'échelle, le mobilier l'est. Une liste de dix noms couvre plusieurs mètres carrés fictifs. Le plan à l'échelle du bâtiment est le plan listes masquées, et le logiciel le dit sur chaque impression portant les listes.
Tant qu'aucune dimension de salle n'est saisie, le plan est exact dans ses tables et arbitraire dans son cadre. Le logiciel porte alors, sur le plan et sur chaque impression, la mention « disposition relative — l'encombrement dans la salle n'est pas vérifié ». Une mention imprimée coûte une ligne ; un montage à refaire coûte une soirée.
Une fois les dimensions saisies, le logiciel mesure et rapporte, il ne déclare pas conforme. Le mot « conforme » est absent de l'interface tant que les dégagements de référence ne sont pas établis.
Toute impression à échelle réduite porte une échelle graphique, non la seule mention « 1:100 » : une impression ajustée à la page change le facteur sans prévenir, et le chiffre devient un mensonge tandis que la règle graphique reste juste. La planche de badges, qui est à l'échelle 1, porte la sienne sur sa feuille de contrôle (§ 11.3) : la planche ne laisse pas un millimètre libre, et une graduation tracée sur un axe de coupe laisserait des repères de 8 mm sur le carton fini — elle ne disparaîtrait pas à la coupe.
Pourquoi poser l'unité maintenant, alors que la première itération n'exige aucun contrôle d'encombrement : le coût est nul aujourd'hui, et plus tard c'est une migration silencieuse — tous les fichiers enregistrés porteraient des coordonnées dans une unité arbitraire, que rien ne distingue des nouvelles.
8. Les événements : fichiers, enregistrement, historique
8.1 Un événement, un fichier, une liste
Chaque événement vit dans son propre fichier d'état, complet et autonome. Ce fichier seul suffit à rouvrir, imprimer et transmettre ; l'historique vit à côté et n'est jamais nécessaire pour ouvrir.
Ce que le fichier porte :
| participants | avec identifiant entier, titre pressenti, notes |
| tables | capacité et son état : sieges: null (suit le défaut) ou un entier (surcharge) |
| le nombre de sièges par défaut de l'événement | |
| tours, réglages, réservations avec leur portée, titres de place | |
| placements engendrés, celui qui est retenu, état du plan | |
| positions en centimètres, et l'unité déclarée | |
| filiation : nom et identifiant de la source, instant d'origine |
Ce que le fichier ne porte pas : ni le mode, ni le cadrage. Une liste de ce qui est dedans, sans son complément, laisse l'implémenteur ajouter.
Le logiciel ouvre sur une liste des événements, avec pour chacun le nom, la date, l'état, le nombre de participants, la dernière modification et le chemin. Un répertoire vide n'affiche pas une liste vide : il présente la commande de création et les démonstrations — une liste vide sans issue est le premier écran que voit un opérateur qui lance l'exécutable.
Le logiciel n'ouvre qu'un événement à la fois, et ferme le précédent sans question : il n'y a rien à enregistrer.
8.2 L'enregistrement et l'entrée d'historique sont le même événement
Toute modification passe par une commande nommée. Quand elle s'achève, le logiciel ajoute une entrée au journal, puis écrit le fichier d'état. Jamais l'un sans l'autre.
un geste achevé ⇔ une entrée d'historique ⇔ un état enregistré
Il n'existe donc aucun état sur le disque que l'opérateur ne puisse nommer, et aucun travail « non enregistré ».
L'ordre n'est pas indifférent : le journal d'abord. Une panne entre les deux laisse un journal en avance d'une entrée, cas dont le logiciel se relève seul en comparant les numéros de révision. L'ordre inverse perdrait le geste sans trace.
Ce qui achève une commande est la fin du geste, jamais un minuteur : le relâchement du pointeur, la sortie d'un champ, la validation d'un formulaire, la fin d'un calcul. Un intervalle de temps ferait tomber la frontière au milieu d'un geste — une personne enlevée d'un siège et pas encore posée.
Le fichier de réglages locaux du § 8.5 échappe nommément à cette règle : il ne porte aucune entrée d'historique, et le logiciel diffère son écriture plutôt que de l'écrire à chaque image d'un zoom. Lui appliquer la règle écrirait un fichier par image de molette.
Le coût d'un enregistrement complet est un seuil mesuré, non un ordre de
grandeur posé de tête : la réécriture intégrale du fichier d'état à chaque
entrée est la contrainte réelle qui gouverne le format (§ 8.9). La mesure se
prend sur la grande démonstration, sur la plateforme livrée, et le test échoue
au-delà (§ 14.1 ; série node:long du § 14.4).
Est une entrée exactement ce qui change le contenu du fichier. Changer de tour affiché, zoomer, mettre en évidence, masquer les listes n'en sont pas. Déplacer une table en est une : sa position est une donnée du plan.
Chaque entrée porte un libellé en français, figé à l'écriture — « Déplacé Camille Roy de la table 4 à la table 7, tour 2 ». Jamais reconstruit ensuite : les objets nommés peuvent avoir disparu, et un historique qui affiche « déplacé ‹participant supprimé› » ne sert plus à rien.
L'ordre des entrées est celui des lignes, jamais celui des horodatages. Une horloge reculée rendrait le tri non monotone.
8.3 Le retour arrière : on revient à un instant
Le logiciel restitue un état, il ne défait pas un geste. L'opérateur, lui, lit une liste de gestes : chaque instant est présenté par le libellé qui l'a produit.
Défaire en appliquant un inverse exigerait d'écrire et d'éprouver un inverse par commande, et un inverse faux corrompt le plan sans rien signaler. Un état restitué ne peut pas être faux : il a existé.
Le journal ne se tronque jamais. Revenir ajoute une entrée dont l'état résultant est l'état visé. L'historique classique, qui efface la suite dès qu'on modifie après avoir défait, perd exactement le travail qu'un opérateur pressé vient de mettre de côté pour essayer autre chose.
Le fil courant se définit par un parcours arrière : le prédécesseur d'une entrée ordinaire est celle qui la précède ; le prédécesseur d'une entrée de retour est l'entrée qu'elle vise. Défaire vise le prédécesseur sur ce fil. Refaire n'existe que tant que les dernières entrées sont des retours. La liste distingue le fil courant des fils abandonnés : un opérateur qui croit remonter son fil et atterrit sur un fil mort obtient un état légitime, jamais celui qu'il visait.
Les jalons. L'opérateur peut nommer l'instant courant, et filtrer l'historique sur les instants nommés. Nommer ne modifie pas le contenu : c'est une ligne de marque, pas une entrée, sinon l'équivalence du § 8.2 serait fausse. Le logiciel nomme automatiquement la création, chaque changement d'état, et l'instant qui précède chaque génération.
8.4 Le mode lecture, par défaut
Le logiciel ouvre tout événement en lecture. Toujours, y compris celui qui était en écriture à la fermeture précédente. Le mode n'est pas écrit dans le fichier : c'est une posture de la séance, pas une propriété du plan.
En lecture, aucun geste ne modifie quoi que ce soit, et le fichier n'est pas écrit. Ouvrir un événement ne réécrit jamais son fichier d'état.
Ce qui reste vivant en lecture : le sélecteur de tour, le zoom, le cadrage, l'ajustement, le plein écran, la mise en évidence, l'affichage des listes, la page de qualité — son export CSV des indicateurs et son impression (§ 11.7) —, la comparaison des propositions, la lecture de l'historique, l'impression, et la production de la planche de badges, qui n'écrit aucun fichier d'événement (§ 11.6). C'est la condition pour que la protection tienne : si naviguer exigeait de passer en écriture, l'opérateur y resterait en permanence et le mode lecture ne protégerait plus rien.
On en sort par un seul geste délibéré — une commande « Modifier » à place fixe — et le logiciel y retourne seul après dix minutes d'inactivité. Ce retour ne coûte rien et ne perd rien, puisque tout geste achevé est déjà sur le disque.
Le compteur d'inactivité ne court pas pendant un geste : il est suspendu
tant qu'un pointeur est enfoncé, qu'un champ contient une saisie non validée ou
qu'un calcul tourne. Le retour automatique ne peut donc jamais tomber
pendant un geste ; le retour explicite, lui, annule un glissement en cours
comme le ferait un pointercancel.
Le mode est visible sur la surface de travail elle-même — un cadre et un libellé permanents autour du plan — et non dans un coin de barre d'outils : l'opérateur regarde le plan.
S'y ajoute un bandeau d'application présent sur tout écran, qui porte la version et la provenance de la construction (§ 18.6) et, quand un plan est ouvert, le libellé de mode. Les deux ne font pas double emploi : le cadre signale le mode là où la main agit ; le bandeau porte la version jusque sur la liste des événements, qui est le premier écran d'un signalement sur deux.
Le passage en écriture est une étape nommée du parcours (§ 2.1, étape 1b), et non un sous-entendu : huit des douze étapes sont modifiantes, et toutes sont refusées tant qu'il n'a pas eu lieu.
Fermer en mode écriture ne coûte rien, et le logiciel ne pose jamais la question « enregistrer les modifications ? » : un opérateur ne sait pas ce que contient un tampon.
Seconde protection, à l'intérieur du mode écriture : un clic n'est pas un glisser. Le déplacement ne commence qu'au-delà d'un seuil de mouvement mesuré sur la tolérance réelle d'un pavé tactile, et s'abandonne par Échap.
8.5 Mode et état : deux axes, pas une énumération
Le mode n'est pas un cinquième état. L'état décrit où en est le plan et voyage avec le fichier ; le mode décrit ce que la main a le droit de faire et ne quitte pas la séance.
Il n'y a donc jamais trois comportements à tenir, mais deux — lecture et écriture — dont l'un est atteint par deux chemins : le défaut d'ouverture, et l'état bloqué qui le force en remplaçant « Modifier » par « Débloquer ».
Bloqué et lecture se ressemblent et diffèrent en nature. Bloqué est inscrit dans le fichier, vaut pour toutes les séances, et ne se lève que par une commande nommée : on bloque un plan pour le distribuer. Lecture est la posture par défaut, à un clic de l'écriture. Les confondre donnerait l'un des deux désastres : déverrouiller un plan distribué deviendrait aussi facile qu'un clic involontaire, ou déplacer une personne dans un brouillon exigerait de « débloquer ».
Le cadrage se range avec le mode, hors du fichier — dans un fichier de
réglages locaux, reglages_locaux.json à la racine du dossier de travail
(§ 8.6), distinct des fichiers d'événements. Écrire le zoom dans le fichier
d'état contredirait l'équivalence du § 8.2, et zoomer sur un plan bloqué
écrirait dans le fichier d'un plan qui refuse toute modification. Ce réglage
n'est ni transmis avec le fichier, ni restitué par un retour arrière.
Ce fichier porte deux choses, et deux seulement :
| réglage | portée |
|---|---|
| le cadrage | indexé par identifiant d'événement |
| le format de papier (§ 11.2) | global à la machine |
Le format de papier décrit le bac de l'imprimante, pas le plan : l'écrire dans le fichier d'événement ferait voyager le bac d'un bureau avec le plan. Le logiciel le demande à la première production d'une planche et ne le redemande plus — le redemander chaque fois apprend à cliquer sans lire (§ 5.9). En mode portable (§ 8.6), ce fichier suit la clé avec les événements.
Son écriture est différée, et elle échappe nommément à la règle « fin du geste, jamais un minuteur » du § 8.2.
8.6 Les fichiers
Emplacement. Ces règles sont des propriétés de l'implémentation
electron, celle qui est livrée. Sous web, « à côté de l'exécutable »
ne désigne rien, et la plateforme l'annonce déjà au démarrage (§ 8.8).
Le logiciel détermine son dossier de travail une fois par séance, par une règle écrite, et l'affiche :
- Il calcule le dossier de l'exécutable à partir du chemin que le lanceur
portable publie, jamais à partir du chemin du processus en cours
d'exécution. Un exécutable portable est une archive auto-extractible : le
processus tourne depuis un dossier temporaire que le système efface, et un
data/créé là emporterait la soirée sans un message. Que le lanceur publie bien ce dossier, et sous quel nom, se vérifie dès la première semaine, au même titre que la construction depuis Linux (§ 16). - Si ce dossier se trouve sous un emplacement de données applicatives par
utilisateur —
AppDataet ce qu'il contient, un dossier temporaire —, la règle s'arrête ici et le logiciel passe au point 5. Un installateur qui rangerait l'exécutable sousAppDatay ferait réussir l'épreuve d'écriture, et le logiciel créerait son dossier de données exactement là où cette section l'interdit. Le livrable est pour cette raison un exécutable portable à fichier unique, non un installateur (§ 16). - Sinon, le logiciel éprouve l'écriture dans
data/à côté de l'exécutable, qu'il crée au besoin : il écrit un fichier témoin, le relit, l'efface, et efface un témoin resté d'une séance précédente. Un contrôle de droits ne répond pas à la question posée — les droits effectifs dépendent d'héritages, d'appartenances de groupe, de stratégies d'antivirus, et un support amovible en lecture seule présente un dossier d'apparence inscriptible. Une écriture suivie d'une relecture répond. - L'épreuve réussit →
data/est le dossier de travail. C'est le mode portable : l'exécutable et les événements se déplacent ensemble, et la sauvegarde est la copie d'un seul dossier. - L'épreuve échoue, ou le point 2 l'a interrompue → le dossier de travail est un dossier nommé d'après le produit, dans les Documents de l'opérateur. Le logiciel dit pourquoi, une fois, à la première ouverture, sans bloquer.
Le dossier retenu est éprouvé à son tour, et c'est cette épreuve qui le crée : le dossier des Documents naît à la détermination, avant que la liste des événements s'ouvre, et non à la première écriture. Aucun geste ne crée ensuite le dossier où il écrit : un dossier de travail qui disparaît en cours de séance ne renaît pas vide, en silence. Si l'épreuve échoue, le logiciel le dit avant d'ouvrir la liste, puisqu'aucun geste ne s'y écrirait.
Ce qui arrive ensuite est aussi réglé.
- Si
data/existe, contient des événements et cesse d'être inscriptible, le logiciel ne bascule pas en silence : il nomme le dossier, dit ce qui l'empêche, et demande. Basculer en silence ouvrirait sur une liste où le plan de la veille a disparu, et l'opérateur conclurait à une perte. - La même règle vaut en cours de séance, pas seulement au démarrage : une clé se retire. Quand une écriture échoue, le logiciel nomme le fichier et le dossier, et propose d'écrire ailleurs, dans un dossier qu'il crée au besoin puisque l'opérateur l'a désigné. Il énonce alors que l'équivalence du § 8.2 est rompue par le support, au lieu de laisser croire que le dernier geste est sur le disque.
- Si le dossier de travail est neuf et vide alors que l'autre emplacement contient des événements, le logiciel le dit et propose de les importer (§ 8.7) plutôt que d'ouvrir sur la commande de création (§ 8.1). L'import copie : le logiciel annonce que les originaux restent où ils sont, sinon deux copies du même événement dérivent sous le même identifiant interne.
- Le mode portable affaiblit une garantie du § 8.8 sur un support amovible, et le logiciel l'annonce.
Dans les deux modes, le logiciel affiche le chemin du dossier de travail en clair dans la liste des événements, et une commande l'ouvre dans l'explorateur.
Ce que cette section défend n'est pas l'emplacement Documents : c'est que l'opérateur puisse copier un événement sur une clé, le joindre à un courriel et le restaurer depuis sa sauvegarde sans le logiciel. Cette propriété tient dans les deux modes, parce qu'elle vient de la forme — des fichiers ordinaires dans un dossier ordinaire, sans base de données ni format propriétaire — et non du chemin.
Ce que le logiciel refuse, inchangé : jamais de répertoire de données caché,
jamais AppData, jamais un emplacement que l'opérateur ne peut pas nommer.
Le profil de Chromium n'est pas un répertoire de données. Chromium, qu'emporte la coquille Electron, range par défaut son profil — cache, préférences, stockage de la page — dans un dossier persistant des données applicatives de l'utilisateur. Le logiciel n'y garde rien : la coquille pose ce profil dans un dossier neuf du dossier temporaire du système, et l'efface à la sortie ; chaque démarrage efface aussi ceux des séances terminées, que Chromium a récrits après la sortie ou qu'une séance coupée a laissés. Un exécutable portable ne laisse ainsi, hors du dossier de travail, rien qui survive à la séance suivante.
Deux fichiers par événement :
| fichier | contenu | écriture |
|---|---|---|
<nom>.gtt.json |
l'état courant, complet | réécrit en entier à chaque entrée |
<nom>.gtt-journal.jsonl |
les entrées d'historique | ajout en fin, une ligne par entrée |
Les deux portent l'identifiant interne de l'événement, et c'est lui qui les apparie — jamais leurs noms.
Le nom du fichier dérive du nom de l'événement — un dossier d'identifiants
opaques est inutilisable dans un explorateur. La dérivation retire les
caractères que Windows refuse, refuse les noms réservés quelle que soit la
casse, compare les collisions sans égard à la casse (le système de
livraison y est insensible, celui de développement non), borne le chemin
complet sur le plus long chemin que l'événement écrit, et retombe sur un nom
générique quand la dérivation ne laisse rien. Ce plus long chemin assemble les
plus longues parties qu'un fichier de l'événement peut porter : le dossier daté
de la corbeille (§ 8.7) au rang de collision le plus long — deux suppressions
dans la même seconde se départagent par _2, _3… jusqu'à _99 —, le plus long
suffixe, celui de la génération de secours, .gtt.json.precedent, dix-neuf
caractères, et le suffixe .ecriture de l'écriture atomique (§ 8.8) :
corbeille/AAAA-MM-JJ_HH-MM-SS_99/<nom>.gtt.json.precedent.ecriture, soixante
et un caractères outre le nom. .gtt-journal.jsonl n'en compte que dix-huit, et
.gtt.json neuf : une borne posée sur l'état ou sur le journal laisserait
déborder tout le reste. La dérivation reçoit la racine du dossier de travail
en paramètre, celle-ci n'étant plus connue à l'écriture du code, et son test
l'exerce sur les deux issues. L'autorité reste le nom inscrit dans le
fichier.
Pourquoi l'historique n'est pas dans le fichier d'état, pour deux raisons distinctes. La taille : un plan de 260 personnes sur 4 tours pèse de l'ordre de 250 ko avec une vingtaine de propositions ; 500 entrées en instantanés complets pèseraient de l'ordre de 125 Mo — d'où les correctifs, avec un instantané tous les cinquante au plus, pour qu'aucune restitution ne demande plus de cinquante applications. Le couplage : un journal illisible n'empêche jamais d'ouvrir le plan ; transmettre un plan ne transmet pas quatre cents gestes portant les noms des personnes retirées ; vider l'historique est le remplacement d'un fichier.
Ces ordres de grandeur ne valent que sous la forme positionnelle du § 8.9. Sous une forme nommée par place, le fichier d'état dépasse le mégaoctet — et ce n'est pas l'historique qui casse alors, c'est le § 8.2, qui réécrit le fichier en entier à chaque entrée, c'est-à-dire au relâchement de chaque pointeur.
Une ligne illisible arrête la lecture : le logiciel écarte cette entrée et tout ce qui suit, et annonce le nombre. Reprendre après elle ferait suivre un correctif à un instant qui n'est pas le sien.
Taille bornée : au moins les cinq cents dernières entrées, et au-delà élagage sur frontière d'instantané. Le plancher l'emporte sur le plafond — un historique qui affiche un instant qu'il ne sait plus reconstruire est pire que pas d'historique.
8.7 Copier, renommer, importer, supprimer
Copier produit un nouvel événement avec une identité propre. Le logiciel demande le nom avant de créer ; sans cela les noms s'empilent en « (copie) (copie) ».
L'historique ne se copie pas. La copie démarre sur une entrée unique : « Créée par copie de ‹nom›, à l'instant ‹libellé› ». Rejouer les entrées de l'original promettrait un retour arrière qui ramènerait le contenu de l'original — exactement ce dont l'opérateur voulait diverger. La filiation est conservée comme un fait inscrit dans le fichier.
La copie d'un plan bloqué n'est pas bloquée : son état se déduit de son contenu. Copier un plan bloqué est le geste même de « le plan distribué reste figé, je travaille sur une variante ».
Renommer change les deux fichiers : le logiciel écrit d'abord la paire complète sous les nouveaux noms, puis supprime l'ancienne — jamais l'inverse. Une panne laisse alors deux paires, ce qui se voit et se répare, au lieu d'aucune.
Importer un fichier venu d'ailleurs le copie dans le dossier de travail. Travailler en place écrirait dans un dossier de téléchargements que l'opérateur vide régulièrement.
Supprimer déplace tous les fichiers dans une corbeille/ datée. Le logiciel
ne la vide jamais de lui-même et affiche ce qu'elle contient : un opérateur qui
croit avoir supprimé les coordonnées d'une personne les a en réalité déplacées.
8.8 Ce qui résiste à une panne
Ces garanties sont des propriétés de l'implémentation electron, celle qui
est livrée. L'implémentation web, de développement, n'offre ni renommage
atomique ni verrou, et le logiciel l'annonce au démarrage sur cette
plateforme.
- Écriture atomique : fichier temporaire dans le même répertoire, vidé sur le disque, puis renommé par-dessus. Sous Windows ce renommage échoue tant qu'un antivirus ou un agent de synchronisation tient la cible ouverte : le logiciel réessaie, puis renonce en nommant le fichier, et un renommage qui échoue ne détruit jamais la cible.
- Une génération de secours sous
.precedent. Le renommage atomique protège d'une panne ; il ne protège pas d'une écriture complète mais fautive. - À l'ouverture, le logiciel ne repart jamais de zéro en silence : il nomme le fichier illisible et propose la version précédente, puis le dernier instant du journal. Créer un événement vide est la défaillance qui perd une soirée, parce que l'enregistrement automatique la rend définitive en une seconde.
- Version de format. Un fichier plus récent s'ouvre en lecture seule sans passage possible en écriture ; un fichier plus ancien se convertit au premier passage en écriture, jamais à l'ouverture.
- Une seule séance en écriture, le verrou se prenant à l'entrée en mode écriture et non à l'ouverture.
- Contrôle de cohérence : l'en-tête annonce ses comptes, sa révision, sa
version de format et
produit_version(§ 18.6) ; l'analyse syntaxique seule ne distingue pas un fichier complet d'un fichier plausible. Le contrôle s'étend aux placements (§ 8.9) : longueurs déclarées contre listes écrites, identifiants vérifiés en multiensemble par tour, réserve comprise. - Sérialisation canonique : ordre de clés fixé, et aucune valeur que le
sérialiseur dérive de l'horloge ou d'un tirage au moment d'écrire. La règle
porte sur le sérialiseur, jamais sur la provenance de la donnée : une
valeur tirée ou horodatée une fois, puis devenue partie de l'état — graine
d'une proposition,
produit_version, identifiant d'événement — est une donnée ordinaire. Lue comme un interdit sur la provenance, cette règle interdirait d'enregistrer la graine, et une proposition retenue cesserait d'être régénérable. Lue comme un interdit sur le sérialiseur, elle garde tout son effet : ce qu'elle empêche est l'horodatage glissé à chaque écriture, qui fait enregistrer du bruit à chaque geste. - Le fichier se partage en en-tête et charge utile. En-tête : comptes,
révision, version de format,
produit_version. Charge utile : tout le reste. La canonicité octet pour octet est exigée du fichier entier, pour un binaire donné ; mais les deux comparaisons qui traversent les constructions — l'empreinte des fichiers de démonstration (§ 15.5, point 5) et l'aller-retour sur les fichiers livrés (§ 8.9) — portent sur la charge utile seule. Sans cette restriction,produit_versionfait échouer l'empreinte à chaque livraison : le test exigerait alors de régénérer les démonstrations à chaque incrément de version, c'est-à-dire exactement la dérive que le § 15.5 existe pour empêcher, et le geste conscient qu'il réclame deviendrait mécanique, donc aveugle. - Le journal note la construction qui change : quand
produit_versiondiffère de celle de l'entrée précédente, l'entrée de journal la porte. Le coût est d'un champ par remplacement d'exécutable ; sans lui, un fichier transmis pour diagnostic annonce la construction qui a écrit en dernier, et rien ne dit laquelle a produit l'état suspect. - Sur un support amovible, la garantie d'écriture atomique est annoncée comme
affaiblie : le renommage par-dessus n'y a pas la même valeur que sur le
volume système, et le support peut disparaître entre l'écriture et le
renommage. Le logiciel l'annonce comme il annonce déjà les limites de la
plateforme
web.
8.9 La forme des placements dans le fichier
« En clair » veut dire non chiffré, pas lisible à l'œil. Le fichier reste du texte, ouvrable dans un éditeur, sans clé ni outil.
Le logiciel ne comprime pas. Trois raisons, dont une seule suffirait. Le journal est en ajout de ligne en fin (§ 8.6) et une archive comprimée ne s'étend pas ainsi. Un octet abîmé dans un flux comprimé emporte tout ce qui suit, là où une forme texte permet d'écarter une proposition et d'ouvrir le reste. Et les correctifs du § 8.6 reposent sur la localité : une personne déplacée touche deux entrées de deux listes, tandis qu'une recompression réécrit le flux entier et rend le correctif aussi gros que l'instantané.
La compression ne détruirait pas la canonicité du § 8.8 — un compresseur à niveau fixé est déterministe. L'argument est ailleurs, et il est celui ci-dessus.
La compacité ne s'applique qu'aux placements. Le fichier se partage en deux régions, et la frontière suit une seule question : ce qui se perd ici se retrouve-t-il ?
| région | forme | pourquoi |
|---|---|---|
| participants, tables, tours, réservations, titres de place, filiation | champs nommés, un objet par chose | c'est de la saisie d'opérateur : perdue, elle est perdue, et elle doit se lire à l'œil quand un fichier s'abîme. Elle est petite — de l'ordre de 40 ko pour 260 personnes avec courriels |
| placements engendrés | forme positionnelle | c'est la partie volumineuse, et la seule qui se recalcule : une proposition perdue coûte une relance |
La forme positionnelle
Une proposition porte un identifiant entier séquentiel, et les propositions
sont rangées en liste ordonnée — jamais en objet indexé par une clé, dont
l'ordre n'est pas garanti par la forme. L'identifiant vient de la place dans la
suite de graines dérivées (§ 5.7), et ne dérive ni de l'horloge ni d'un tirage.
Il se compte à partir d'un compteur du fichier, prochainsIds.proposition, qui
dépasse tout identifiant de proposition jamais attribué, celui d'où vient le
placement retenu compris : une génération numérote ses propositions dans
l'ordre de ses graines à partir de lui, puis le porte au-delà de la dernière. Le
compteur ne recule jamais — ni quand une commande efface les propositions
(§ 5.7), ni quand une main l'abaisse dans le fichier, l'ouverture le relevant
au-delà de chaque identifiant qu'elle lit. Les générations s'accumulent ainsi
sans qu'un identifiant se répète ni revienne, et le retenu désigne toujours la
même proposition d'origine.
Chaque proposition déclare, une fois :
- la liste ordonnée des identifiants de table sur laquelle elle est bâtie ;
- la liste des capacités correspondantes ;
- le nombre de tours ;
- l'ensemble des identifiants de participants qu'elle place ;
- le drapeau
siegesAttribues: ses sièges sont-ils attribués (§ 5.3), l'ordre d'une liste de table étant alors celui des sièges ; - sa graine dérivée (§ 5.7), le compte d'arrêt et la longueur de l'historique d'acceptation de la recherche qui l'a produite : les trois réglages qui la déterminent.
Ces trois champs sont le support de « à graine et entrée égales, placement
identique » (§ 5.10) : à configuration égale et pour une même version du moteur
(produit_version, § 8.8), la régénération d'une proposition à partir d'eux
seuls rend son plan, quel que soit son rang dans sa génération. Sans eux, cette
phrase n'existe que dans un scénario de pilotage, et une proposition retenue
cesse d'être régénérable. La longueur de l'historique a une valeur par défaut,
mais la proposition porte celle qui l'a produite : régénérer sous une valeur
supposée rendrait une autre proposition, sans le dire. Les trois champs ne
déterminent le plan que pour l'algorithme de recherche qui l'a produit, dont
l'ordre des tirages et les règles d'acceptation font partie du résultat : une
proposition produite par une autre version du moteur n'est pas garantie
régénérable.
Puis, par tour : la liste des tables, chacune étant la liste des
identifiants entiers de ses occupants dans l'ordre des sièges ; et la
réserve, la liste des identifiants assis nulle part à ce tour. Le numéro de
siège n'est pas stocké : il est la position dans la liste. Quand les sièges sont
attribués, une chaise vide avant le dernier occupant, siège 1 compris, s'écrit
null à sa position, et une liste ne finit jamais par null : retirer une
personne libère sa chaise, et aucun autre siège ne change — le placement se
prépare, il ne suit pas la salle.
Sans attribution, aucune liste ne porte null.
Quand les sièges ne sont pas attribués (§ 5.3), l'ordre à l'intérieur d'une table ne porte aucune information : le logiciel l'écrit trié par identifiant croissant. Sans cette règle, deux états identiques produisent deux fichiers différents, et le § 8.8 est violé par la seule forme du stockage. C'est le drapeau de la proposition qui décide, jamais le réglage courant de l'événement : changer le réglage n'efface pas l'ordre des sièges d'une proposition déjà produite. Le placement retenu a la même forme et son propre drapeau, avec l'identifiant de la proposition dont il vient. L'ensemble des participants et chaque réserve se trient par identifiant croissant dans tous les cas : ce sont des ensembles.
# illustration, annotée ; le fichier livré est du JSON strict
propositions: [
{ id: 3,
graine: 48271, arret: 20000, historique: 1000,
tables: [7, 12, 2, ...], identifiants de table (§ 4)
capacites: [8, 8, 7, ...],
tours: 4,
participants: [1, 2, 3, ..., 260],
placement: [
{ sieges: [[12,45,7,101,3,88,56,9], [21,64,...], ...], tour 1
reserve: [] },
{ sieges: [[12,33,...], ...], tour 2
reserve: [177] },
...
] } ]
Ce que cela économise
Sur la grande démonstration — 260 participants, 4 tours, une vingtaine de propositions (§ 15.1), soit 1 040 places par proposition :
| forme | par proposition | vingt propositions |
|---|---|---|
un enregistrement nommé par place (tour, table, siege, participant) |
de l'ordre de 50 ko | de l'ordre de 1 Mo |
| positionnelle, en-têtes compris | de l'ordre de 5 ko | de l'ordre de 100 ko |
Un facteur de l'ordre de dix. Trois conséquences sur des chiffres écrits ailleurs :
- Les ordres de grandeur du § 8.6 présupposent cette forme. Les « 250 ko » d'un fichier d'état sont atteints par la forme positionnelle, et dépassés d'un ordre de grandeur par la forme nommée, qui conduit au mégaoctet.
- Le coût d'un identifiant opaque est décuplé, non quadruplé (§ 4) : sous la forme positionnelle, un identifiant de trente-six caractères remplace quatre octets par trente-neuf.
- La contrainte réelle est le § 8.2, pas l'historique. Avec un instantané tous les cinquante, cinq cents entrées coûtent une dizaine d'instantanés — gros, pas rédhibitoire, même sous la forme nommée. Ce que la forme nommée casse est ailleurs : le § 8.2 réécrit le fichier d'état en entier à chaque entrée d'historique, c'est-à-dire au relâchement de chaque pointeur. Écrire, vider sur le disque et renommer 140 ko est une chose ; faire de même avec plus d'un mégaoctet, sur un support qu'un antivirus inspecte à chaque fichier temporaire créé, en est une autre — et la première victime serait l'équivalence « un geste achevé ⇔ un état enregistré », qu'on serait tenté de relâcher par un minuteur, ce que le § 8.2 interdit nommément. Le coût d'un enregistrement se mesure sur la grande démonstration, sur la plateforme livrée, et la mesure est écrite en commentaire avec ce qu'elle mesure (§ 14.1). Un seuil posé de tête ici ne vaudrait rien.
Ce que la compacité coûte, et ce qui le paie
Une forme positionnelle ne se diagnostique pas à l'œil. Un tableau de 260 entiers ne dit pas qui est assis où, et un décalage d'une position déplace tout le monde d'un siège sans qu'aucune syntaxe ne soit fautive.
Quatre exigences rendent ce coût acceptable, et aucune n'est facultative.
- Les longueurs déclarées s'éprouvent contre les listes écrites. Chaque liste de tour a autant de listes de table que la proposition déclare de tables, et chaque liste de table la longueur que la proposition déclare. Ce contrôle est interne : il compare le fichier à lui-même. C'est le contrôle de cohérence du § 8.8 étendu aux placements — l'analyse syntaxique seule ne distingue pas un placement complet d'un placement plausible.
- Les identifiants se vérifient comme un multiensemble, réserve comprise. À chaque tour, chaque identifiant déclaré par la proposition apparaît exactement une fois dans l'union des listes de table et de la réserve. L'égalité d'ensembles ne suffirait pas : sans la réserve stockée et sans le « exactement une fois », un identifiant perdu passerait pour quelqu'un qui attend hors des tables, cas que le § 12.6 prévoit. Le multiensemble attrape le décalage de flux — il duplique un identifiant et en perd un autre —, l'identifiant inconnu, et la personne assise deux fois. Ce qu'il n'attrape pas, et le logiciel ne prétend pas le contraire : une permutation à l'intérieur d'une table, ou l'échange du contenu de deux tables de même capacité, conservent le multiensemble et les longueurs. Rien dans le fichier ne les distingue d'un placement légitime ; ils se voient sur le plan.
- Fautif et périmé sont deux choses, et le logiciel ne les confond pas.
Fautif = le fichier se contredit lui-même : une longueur fausse, un identifiant
dupliqué, un identifiant absent de l'ensemble déclaré. Périmé = la proposition
est cohérente mais ne décrit plus la configuration courante : une table
supprimée, une capacité changée, une personne exclue depuis (§ 4.4), un tour
retiré.
Une proposition fautive est nommée, comptée, et l'événement s'ouvre sans
elle. Une proposition périmée n'est pas écartée : c'est la dérive du
§ 9, qui avertit et ne bloque pas, et le logiciel dit laquelle et pourquoi.
Écarter au chargement une proposition périmée détruirait le travail de
l'opérateur à la suite d'une exclusion, geste qui ne demandait rien de tel.
Le logiciel ne refuse jamais le fichier entier à cause d'une proposition : la
saisie, qui est la partie irremplaçable, est dans l'autre région et n'est pas
touchée.
La règle du journal est l'inverse, et pour une raison qui n'existe pas ici : une ligne de journal illisible arrête la lecture et emporte tout ce qui suit (§ 8.6), parce qu'un correctif suivrait sinon un instant qui n'est pas le sien. Les propositions sont indépendantes les unes des autres ; chacune tombe seule. Le placement retenu fait exception : s'il est fautif, le logiciel le dit en première ligne et propose la génération de secours
.precedent(§ 8.8), parce que c'est le seul placement dont la perte se voit le soir même. - Une commande rend n'importe quelle proposition sous forme nommée, pour lecture et pour signalement d'un défaut. L'outil de diagnostic n'a pas à être le format de stockage — c'est en confondant les deux qu'on paie la lisibilité sur chaque enregistrement, à chaque écriture.
Ce que les épreuves exigent
- Aller-retour : charger, enregistrer, recharger, comparer structure à
structure, sur chacun des fichiers de démonstration livrés. La comparaison
porte sur la charge utile, l'en-tête exclu (§ 8.8). Le balayage refuse de
passer s'il n'examine aucun fichier (§ 14.2). Sur la grande démonstration, cette
épreuve est dans la série
node:long(§ 14.4). - Canonicité : enregistrer deux fois le même état produit deux fichiers identiques octet pour octet, y compris quand les sièges ne sont pas attribués — c'est là que la règle du tri croissant s'éprouve.
- Corruption délibérée : le test abîme un fichier de quatre façons — une longueur fausse, un identifiant dupliqué, un identifiant inconnu, un identifiant manquant sans entrée de réserve — et exige que le logiciel nomme chacune et écarte la seule proposition fautive. Un test qui ne charge que des fichiers bien formés ne peut pas échouer et ne prouve rien (§ 14.2).
- Péremption délibérée : le test exclut une personne, supprime une table, réduit une capacité, et exige que les propositions touchées soient signalées comme dérive et conservées, jamais écartées. C'est le test qui empêche la correction suivante de reclasser le périmé en fautif.
- Coût d'enregistrement : le test mesure la durée d'un enregistrement complet
sur la grande démonstration et échoue au-delà du seuil mesuré du § 8.2. Il écrit
sur un système de fichiers réel, donc il est dans
node:long.
9. Les états d'un plan
| état | ce qu'il signifie |
|---|---|
| Brouillon | on prépare |
| Proposé | des placements existent, aucun n'est retenu |
| Retenu | un placement est en vigueur |
| Bloqué | rien ne peut plus changer |
Brouillon, proposé et retenu n'interdisent rien. Ils ordonnent le travail, se posent en cliquant l'état, et passer de l'un à l'autre ne détruit jamais de données.
Bloqué refuse toute modification — tours, options, tables, participants, import, et jusqu'à la position d'une table à l'écran. La lecture et l'impression restent ouvertes : on bloque un plan précisément pour le distribuer.
La dérive avertit, elle ne bloque pas. Si les participants ou le mobilier changent après qu'un placement a été retenu, le logiciel le signale et laisse l'opérateur décider : lui seul sait si la personne arrivée en retard doit être placée.
Une proposition rendue incohérente par un changement de configuration est une dérive, jamais une corruption (§ 8.9) : elle est conservée, signalée, et le logiciel dit laquelle et pourquoi. L'écarter au chargement détruirait le travail de l'opérateur à la suite d'une exclusion, geste qui ne demandait rien de tel.
10. La liste des participants
10.1 L'import CSV
L'aperçu est obligatoire. Il n'existe pas de bouton « importer directement » : l'aperçu est la seule garde contre une détection d'encodage ou de séparateur qui se trompe sans rien faire échouer, et une garde qu'on peut sauter ne garde rien.
Les colonnes s'associent par leur en-tête, jamais par leur position — un tableur où une colonne a été déplacée importerait sinon les noms dans les appartenances sans rien signaler.
| en-tête | obligatoire | contenu |
|---|---|---|
nom |
oui | |
prenom |
non | |
appartenance |
non | synonymes : organisation, entreprise, équipe |
courriel |
non | |
titre_pressenti |
non | synonymes : titre, rôle — informatif, ne pourvoit aucune place |
exclu |
non | liste fermée ; vide vaut non |
notes |
non | texte libre, jamais interprété |
Les en-têtes se reconnaissent sans tenir compte de la casse, des accents ni des espaces. Deux colonnes reconnues sous le même nom ne se départagent pas toutes seules : le logiciel n'associe ni l'une ni l'autre et demande.
La valeur de exclu appartient à une liste fermée : oui, o, vrai,
1 ou x pour une exclusion ; non, n, faux, 0 ou vide pour aucune ;
sans égard à la casse, aux accents ni aux espaces. Toute autre valeur refuse
la ligne. Traiter l'inconnu comme non placerait dans la salle une personne
qui s'est désistée, et la chaise vide ne se verrait que le soir même.
L'encodage se tranche en quatre temps, dans cet ordre : marque d'ordre
d'octets UTF-8 ; marque UTF-16 ; un octet nul dans les quatre premiers
kibioctets sans marque → refus global avec le remède nommé
(UTF16_SANS_MARQUE) ; sinon décodage UTF-8 strict, et repli sur windows-1252
en cas d'échec.
Une marque déclare l'encodage, et rien ne la dément. Des octets qui ne sont
pas l'UTF-8 ou l'UTF-16 qu'elle annonce — une séquence invalide, un nombre
impair d'octets, un substitut isolé — refusent le fichier (UTF8_INVALIDE,
UTF16_INVALIDE) : un repli sur windows-1252 changerait chaque accent de la
partie valide en caractères parasites, « Benoît » en « Benoît », et un décodage
indulgent sèmerait des caractères de remplacement dans les noms. Quel que soit
le temps qui a décodé, un texte qui porte le caractère nul est refusé
(CARACTERE_NUL) — un collage aussi, qui ne porte pas d'octets : le refus
rattrape un octet nul au-delà des quatre kibioctets, et un fichier que sa marque
fait lire sans erreur sous un autre encodage, la marque de l'UTF-32 LE
commençant par celle de l'UTF-16 LE. Chaque refus d'encodage nomme le même
remède : réenregistrer le fichier en UTF-8.
L'étape 3 n'est pas une précaution de principe : un texte UTF-16 sans marque se décode sans erreur dans l'un ou l'autre des temps suivants. En ASCII, il est de l'UTF-8 valide, chaque octet nul s'y décodant comme le caractère nul ; accentué, il cesse de l'être et retombe sur windows-1252, qui accepte tout octet. Dans les deux cas, les noms arrivent entrelardés de caractères nuls, qui ne font échouer aucun calcul. Un ordre à trois temps produirait ce défaut muet, qui ne se découvre que sur le plan imprimé.
Le séparateur se choisit entre ;, , et la tabulation, en analysant
les vingt premiers enregistrements : le candidat retenu termine sans guillemet
ouvert, donne le même nombre de champs partout, et ce nombre dépasse 1. À
égalité, celui qui donne le plus d'en-têtes reconnus. Quand aucun candidat
ne convient et que la première ligne entière est un en-tête reconnu, le
fichier n'a qu'une colonne — une liste de noms — et chaque ligne est un seul
champ : un nom qui porte une virgule reste entier. Sinon, le fichier est
refusé, et le refus nomme ce qui écarte le séparateur.
Les deux dernières conditions ne sont pas des raffinements. Sur un fichier séparé par des virgules,
;donne lui aussi un nombre de champs parfaitement constant : un. Une règle qui ne retiendrait que la constance importerait chaque ligne entière dans la colonnenom.
Les appartenances se réconcilient sur la même forme normalisée que la clé de doublon — espaces réduits, casse et accents ignorés ; la première orthographe rencontrée s'affiche. Le rapport d'import nomme chaque groupe d'orthographes fondues et son nombre de lignes : une fusion silencieuse est aussi fausse qu'une séparation silencieuse. Une appartenance vide vaut absence, jamais une appartenance nommée « ».
Sans cette réconciliation, deux orthographes d'une même organisation deviennent deux appartenances : « séparer les appartenances » cesse de séparer deux collègues, G est gonflé, et A_max avec lui. Le défaut est muet — aucun indicateur ne s'en aperçoit.
La ligne invalide : importer le reste. Le logiciel importe les lignes
valides et réexporte les refusées en un CSV reprenant les colonnes
d'origine, augmenté de ligne et motif — deux colonnes qu'aucun en-tête ne
reconnaît, donc le fichier corrigé se réimporte tel quel. ligne porte le
numéro de l'enregistrement, en-tête compris : celui qu'un tableur affiche, un
champ cité sur plusieurs lignes du texte n'y comptant qu'une fois. Sur 260
lignes, un refus global coûterait l'import entier pour une faute en ligne 213.
Hors de l'encodage et du séparateur, quatre cas restent des refus globaux :
aucun en-tête reconnaissable, aucune colonne associée à nom, zéro ligne
valide, et un guillemet resté ouvert jusqu'à la fin du texte
(GUILLEMET_OUVERT, avec l'enregistrement où il s'ouvre) — tout ce qui le suit
tiendrait dans un seul champ, et deux personnes se fondraient en une.
L'import entier est une seule entrée d'historique.
Les doublons se repèrent sur le triplet (nom, prénom, appartenance) normalisé, et le logiciel ne fusionne jamais tout seul : deux personnes portent le même nom, et la fusion silencieuse fait disparaître un convive qui se présentera quand même.
Mettre à jour n'efface pas : un champ vide dans le fichier laisse la valeur existante. Un fichier ne portant que les noms effacerait sinon tous les courriels, d'un geste qui s'annonce comme une mise à jour.
Remplacer annonce ce qu'il détruit, réservations et titres compris : « 260 personnes seront supprimées » laisse croire qu'on ne perd que des noms.
Importer selon l'état : sans avertissement en brouillon et proposé ; avec l'avertissement de dérive en retenu ; refusé en bloqué, le message nommant la commande qui déverrouille.
10.2 La saisie
Seul nom est obligatoire : refuser une personne faute de courriel pousse
l'opérateur à inventer une valeur, qui ne se distingue plus ensuite d'une vraie.
Trois mécanismes rendent tenable la saisie de 260 personnes :
- Le collage en bloc, passé au même analyseur que l'import — un second analyseur « simplifié » divergerait sur les accents et les guillemets, et les tests des deux resteraient verts.
- L'appartenance collante : le champ conserve sa dernière valeur. Saisir les sept personnes d'une organisation, c'est taper sept noms.
- La grille éditable, au clavier, avec tri et filtre. Corriger est ce que l'opérateur fait le plus, la veille et sur place.
La liste s'exporte en CSV dans la forme exacte qu'il importe. L'aller-retour est éprouvé par un test qui importe, exporte et compare champ par champ : il fait du fichier une sauvegarde, et de l'export le gabarit que personne n'a besoin de documenter.
10.3 Les fichiers d'exemple livrés
| fichier | emploi |
|---|---|
exemples/participants_demo_petite.csv |
la liste des participants de la petite démonstration |
exemples/participants_demo_grande.csv |
celle de la grande |
exemples/participants_cas_limites.csv |
éprouve l'analyseur ; n'alimente aucune démonstration |
exemples/participants_cas_limites_cp1252.csv |
même contenu, windows-1252, séparé par des virgules |
Un CSV ne reproduit pas une configuration : un import de participants ne crée ni tables, ni tours, ni réservations, ni titres. Les deux premiers fichiers sont donc produits par l'export CSV à partir des fichiers de démonstration livrés, jamais saisis à la main — la dérive entre l'exemple et la démonstration devient impossible plutôt que surveillée.
Le fichier de cas limites exerce, ligne par ligne : une colonne non reconnue, une personne sans appartenance, une exclusion, un champ cité contenant le séparateur, un champ cité sur deux lignes, un doublon, une personne sans prénom, une paire d'orthographes d'une même appartenance à fondre (§ 10.1), et une ligne refusée faute de nom. Il alimente aussi deux captures d'écran du guide (§ 19.3) — l'aperçu d'import et son rapport —, qui sont les deux écrans où une fusion d'orthographes et un refus de ligne se montrent ensemble.
Tous sont en UTF-8 avec marque d'ordre d'octets, séparés par ; — la forme
qu'un tableur francophone sous Windows rouvre sans manipulation — sauf le
dernier. Noms, organisations et courriels sont inventés ; les courriels
emploient le domaine réservé .test.
11. Les sorties
11.1 Une seule sortie PDF : la planche de badges
Le logiciel produit un seul document PDF : la planche de badges à découper,
distribués à l'accueil et portés autour du cou. Toute autre sortie se lit à
l'écran et s'imprime par le navigateur ou par l'hôte electron (§ 11.7).
Il le produit en mémoire, par une bibliothèque embarquée. L'argument est
celui du § 14.4, et il est le seul qui tienne : une bibliothèque embarquée
s'exécute sous node, donc dans le cycle « à chaque modification ». Un test
de quelques secondes affirme que la boîte de page mesure 210 × 297 mm à moins de
0,1 mm près, que la planche porte quatre cellules de 105 × 148 mm, que les
filets de coupe tombent à 105, 148 et 296 mm, que l'ordre des badges est celui
qu'annonce l'imposition choisie, et que les caractères accentués se retrouvent
dans la couche de texte — pas dans une image.
Le chemin d'impression, lui, exige de démarrer l'application et un moteur de rendu : il appartient au niveau « navigateur », éprouvé avant chaque livraison et non à chaque modification. Un contrôle qui ne tourne qu'avant une livraison ne tourne pas, et la justesse du badge est la seule justesse de sortie qui soit géométrique : une liste mal paginée se réimprime, soixante badges coupés trop court sont refaits à la main pendant que la file s'allonge.
Ce que l'hôte
electronsait faire, et qui ne suffit pas. L'hôte d'impression de la plateforme de livraison accepte un format de page, des marges et un facteur d'échelle fixés par le programme, sans boîte de dialogue : affirmer que « le logiciel ne lit pas les marges » serait faux sur la plateforme livrée. Ce qui manque n'est pas la maîtrise, c'est l'épreuve — elle passe par un rendu, donc par un moteur de navigateur que le cycle court n'a pas. La plateformeweb, elle, n'offre que la boîte de dialogue du navigateur et son « ajuster à la page ».
Ce que cette réduction retire du chemin critique. La bibliothèque n'ingère
plus de SVG : ni <use>, ni <symbol>, ni <tspan> à décalage explicite, ni
plan vectoriel. Le seul point de l'architecture de sortie qui reposait sur une
supposition disparaît, et avec lui le test permanent qui l'éprouvait. Ce qui
reste exigé d'elle est du texte, des filets, des rectangles et une police
embarquée.
Conséquences à tenir :
- La police est embarquée, couvrant le latin étendu du français, ligatures comprises.
- Chaque police embarquée porte une table
ToUnicode. Sans elle, un sous-ensemble de police encode ses glyphes dans un ordre interne : les accents s'impriment correctement et l'extraction rend du charabia. Le test ne distingue alors plus « é absent » de « é inextractible », il échoue sur un défaut qui n'existe pas — ou pire, on le désarme. - Le badge se construit depuis le modèle, jamais depuis le DOM.
- Aucune mise à l'échelle n'est inscrite dans le document : pas de
/UserUnit, pas de boîte de recadrage distincte de la boîte de média. Le test le vérifie sur le fichier produit, et vérifie en outre la position mesurée des filets de coupe : une transformation posée dans le flux de contenu ne se voit sur aucune des deux boîtes. - Les mesures de texte ont une seule autorité : les métriques de la police embarquée, lues par la bibliothèque qui dessine. Le § 14.1 impose de mesurer dans un navigateur ce qui dépend du rendu du navigateur ; le badge n'en dépend pas, et le mesurer là introduirait la seconde arithmétique que le § 13.2 interdit.
11.2 La planche : deux impositions, A4 et Lettre
Le logiciel sait produire sur les deux papiers, et aucun des deux n'est un cas de repli. Le badge est un A6 exact — 105 × 148 mm — dans les deux cas ; ce qui change est le nombre de cellules par feuille et le tracé de coupe.
| papier | format | badges par feuille | axes de coupe | pour 257 badges |
|---|---|---|---|---|
| A4 | 210 × 297 mm | 4, deux colonnes de 105 et deux rangées de 148 | 3 : une verticale, deux horizontales | 65 planches |
| Lettre | 215,9 × 279,4 mm | 2, à plat, 148 de large et 105 de haut | 5 : un encadrement complet | 129 planches |
Le logiciel demande le format à la première production et ne le redemande plus (§ 8.5). Il n'en impose aucun par défaut : choisir pour l'opérateur, c'est produire soixante-cinq feuilles inutilisables quand le bac contient l'autre papier. Le format retenu est écrit sur la feuille de contrôle et dans le nom du fichier (§ 11.3, § 11.6), parce que c'est la seule chose qui survit à l'envoi de la planche à quelqu'un d'autre.
Les deux impositions sont éprouvées à égalité (§ 14.4) : la pagination sur
n = 1, 4, 5, 259, 261 en A4 et n = 1, 2, 3, 257 en Lettre, et la géométrie —
cellules, filets, zone de silence — sur chacune. Une branche qui ne serait
éprouvée que « au cas où » est une branche qui sort fausse le jour où elle sert.
L'A4 : quatre cellules, un millimètre perdu
A4 fait 210 × 297 mm, A6 fait 105 × 148 mm. Deux colonnes de 105 occupent exactement 210 ; deux rangées de 148 occupent 296 et laissent 1 mm.
Le logiciel ne répartit pas ce millimètre dans la hauteur des badges. Il dessine quatre cellules de 105 × 148 mm exactement et laisse le millimètre en bande perdue au pied de la feuille. Le coût est une troisième coupe : une verticale à 105, une horizontale à 148, une horizontale à 296 qui retire la bande. Élargir les cellules à 148,5 économiserait cette coupe et produirait un carton d'un demi-millimètre hors format, que rien ne garantit qu'une pochette taillée à 148 accepte — et la planche cesserait de pouvoir s'annoncer A6. Une sortie qui ment sur son format est pire qu'une sortie qui coûte une coupe, et c'est la même règle qui gouverne le papier Lettre ci-dessous.
La troisième coupe ne coûte rien à qui tranche la rame entière sur un massicot : trois réglages de butée, trois passes, quel que soit le nombre de feuilles. Elle coûte un geste par feuille aux ciseaux. C'est un argument de plus pour l'imposition en pile (§ 11.6) sur les gros tirages, pas une raison de rogner le badge.
L'absence de marge latérale gouverne tout le reste. Le bord d'un badge est le bord de la feuille, où aucune imprimante à jet ou à laser courante ne dépose d'encre. Le logiciel ne dessine donc ni fond perdu, ni aplat de couleur, ni cadre : un fond coloré ressort avec une bande blanche de trois à cinq millimètres sur deux côtés, et la coupe se met à chasser la bande au lieu de suivre un repère.
Le Lettre : deux cellules, un encadrement complet
Le Lettre mesure 215,9 × 279,4 mm : deux rangées de 148 en demandent 296. Un gabarit A4 imprimé sur Lettre perd la rangée du bas. Le logiciel refuse de réduire en silence : il produit deux badges A6 exacts par feuille, à plat — 148 de large, 105 de haut — plutôt que quatre cartons rognés à 139 mm. Sur Lettre les badges ne pavent plus la feuille : le logiciel trace un encadrement de coupe complet, et la coupe demande cinq passes au lieu de trois. Le nombre de feuilles double, et le logiciel l'annonce avant de produire.
Trois badges par feuille Lettre sont géométriquement possibles, et le logiciel ne les produit pas. Deux A6 debout côte à côte occupent 210 × 148, et un A6 couché entre dans la bande de 215,9 × 131,4 qui reste : quatre-vingt-sept feuilles au lieu de cent trente pour deux cent soixante badges. Le prix est une coupe non traversante, qui interdit de trancher la rame entière en un réglage — elle échange un tiers du papier contre un geste par feuille. Elle se décidera sur l'outil de coupe réellement disponible, et pas avant : une imposition de plus est une imposition de plus à éprouver.
La pagination s'éprouve sur les deux impositions, et sur les comptes qui
séparent les deux arrondis (§ 14.2) : n = 1, 4, 5, 259, 261 pour l'A4 —
jamais 260, où la troncature et l'arrondi par excès rendent la même valeur — et
n = 1, 2, 3, 257 pour le Lettre, où 3 sépare les deux calculs que 4
confond. Le compte est celui des participants non exclus : un exclu n'a de
table à aucun tour, son badge serait vide, et c'est ce cas qui fait passer de 65
pages à 65 avec une case blanche, ou à 64, selon l'implémentation. Le témoin de
257 badges donne 65 planches A4 et 129 planches Lettre.
Le format de papier est un réglage local de la machine (§ 8.5), retenu d'une séance à l'autre, jamais écrit dans le fichier d'événement : il décrit le bac de l'imprimante, pas le plan.
11.3 Le découpage, et la feuille de contrôle
Les filets de coupe sont fins, d'une encre neutre, continus, tracés sur les trois axes de coupe. Ils tombent sur le bord du badge, dans la zone de silence, et ce qu'il en reste après la coupe ne porte aucune information. Le logiciel ne pose pas de traits de coupe en marge : il n'y a pas de marge, et il ne prétend pas en poser. Les trois axes ne sont pas deux médianes : 148 n'est pas la médiane de 297, et le troisième filet est celui qui retire la bande perdue.
La zone de silence est la garantie de la coupe. Aucun texte, aucune marque ne s'approche à moins de 8 mm d'un bord de badge. Cette distance couvre deux bornes différentes selon le bord, et le commentaire de la constante le dit, sans quoi la prochaine main la « corrigera » par le calcul :
- sur un bord de coupe : le décalage de l'imprimante, qui déplace la planche entière sur la feuille, plus l'erreur de main, qui déplace la lame ;
- sur un bord de feuille : la marge non imprimable de l'imprimante et le biais d'entraînement du papier, qui n'ont rien à voir avec la coupe.
8 mm est aujourd'hui un défaut de travail, non une constante mesurée au sens du § 14.1, et la constante le déclare. La mesure appartient au niveau manuel du § 14.4 : elle se fait sur les imprimantes visées, avant la livraison, parce qu'un banc de navigateur ne mesure ni un décalage d'imprimante ni une erreur de main. Une zone calculée au plus juste fait couper dans un patronyme.
La cote intérieure des pochettes n'est pas une contrainte de la première itération. Le badge reste un A6 exact, et la troisième coupe reste : la découpe se fait au massicot, où trois passes coûtent trois réglages de butée quel que soit le nombre de feuilles. Si une pochette mesurée tolérait un demi-millimètre, la bande perdue pourrait être répartie et la troisième coupe disparaître — mais la planche cesserait alors de pouvoir s'annoncer A6, et une sortie qui ment sur son format est pire qu'une sortie qui coûte une coupe.
La feuille de contrôle est la page 1 du document, et ne se découpe pas. Elle porte une échelle graphique de 100 mm, repérée tous les 10 mm, les dimensions annoncées du badge, le format de papier, l'imposition retenue, le nombre de planches, le compte de badges produits et le compte attendu, le nom et la date de l'événement, l'horodatage du placement, la version (§ 18.6), et le filigrane BROUILLON s'il y a lieu. L'opérateur la mesure sous une règle avant d'engager la rame : si la planche est sortie « ajustée à la page », la graduation ne fait plus 100 mm et le défaut se découvre sur la première feuille au lieu de la soixante-cinquième.
L'impression est recto seul. Un verso aligné dépend d'un retournement dont le décalage se cumule avec celui du recto, et un badge dont le dos appartient à quelqu'un d'autre n'est pas rattrapable.
11.4 Ce que porte un badge
Le lecteur est la personne qui le porte, debout, le carton à hauteur de poitrine, et qui cherche sa table. Le second lecteur est l'hôte de salle, à un mètre, qui cherche à qui il parle.
| rang | contenu | pourquoi |
|---|---|---|
| 1 | prénom et nom, le plus grand corps de la planche | c'est l'identité, et c'est ce que lit l'hôte à un mètre |
| 2 | une ligne par tour : « Tour 1 — table 12 », et le siège quand les sièges sont attribués | c'est la question à laquelle le badge existe pour répondre |
| 3 | appartenance, un corps réduit, sous le nom | elle distingue deux homonymes à l'accueil |
| 4 | nom de l'événement et date, en pied, corps minimal | deux soupers dans la même salle le même mois |
| 5 | l'horodatage du placement, en pied, corps minimal, lisible en main et non à un mètre | sans lui, deux versions d'un plan circulent autour de deux cous et personne ne dit laquelle fait foi |
Le pied des pages imprimées porte cinq éléments, la version comprise (§ 11.7). Le badge n'est pas une page : il porte le nom de l'événement, sa date et l'horodatage, pas le tour — il les porte tous — et pas la version, qui ne sert à aucun diagnostic au cou d'un invité et dont la place est comptée.
Un itinéraire constant s'écrit en une ligne. Quand une personne occupe la même table à chacun des R tours — le cas de tout participant ancré (§ 4.2) — le badge écrit « Toute la soirée — table 7 » plutôt que R lignes identiques. Le rang 2 ne perd rien : la question « où vais-je au tour 3 » reçoit la même réponse, et la place gagnée revient au nom.
Le filigrane BROUILLON s'applique au badge comme au reste (§ 11.7). C'est la sortie où la confusion coûte le plus cher : elle ne reste pas sur une table, elle part au cou de deux cents personnes.
Ce qui n'y va pas, et la raison de chaque exclusion :
- Le courriel. Un badge se perd, se photographie et se lit par-dessus une épaule. Rien n'oblige à y porter une coordonnée.
- Les notes. Champ libre où l'opérateur écrit ce qu'il ne dirait pas à l'intéressé.
- Le titre pressenti. Il est informatif et ne pourvoit aucune place (§ 4).
- Un titre de place que le moteur a simplement fait occuper. Le § 4.1 pose qu'un titre n'est pourvu que par une réservation, et que dériver le rôle de l'occupation désigne l'animateur par tirage au sort. Imprimer le titre du siège occupé reproduit exactement cette faute, en la portant au cou. Le badge porte le titre uniquement quand une réservation en fait la titulaire, et il l'écrit sur la ligne du tour concerné, non en bandeau — une titulaire d'un seul tour n'est pas animatrice de la soirée.
- Le numéro d'identifiant, sauf le cas d'homonymie ci-dessous.
- Un code à barres ou un code QR. Le logiciel est hors ligne, aucun lecteur n'existe dans la salle, et le code laisse croire qu'un système suit l'entrée.
- Une couleur par table. Des dizaines de tables ne se distinguent pas par des dizaines de teintes, et la planche sort souvent en noir et blanc.
- Les indicateurs, le plafond, la qualité du placement. Ils ne concernent pas la personne qui porte le carton.
Les homonymes que rien ne sépare. Le § 12.6 distingue les homonymes par leur appartenance et navigue par identifiant ; sur un badge, deux personnes de même nom normalisé et sans appartenance ne se distinguent par rien. La pile étant alphabétique, leurs deux cartons sont voisins : l'hôte en tend un au hasard et deux personnes passent la soirée aux tables l'une de l'autre, sans que rien ne le signale. Le logiciel compte ces collisions avant de produire, les nomme, et porte l'identifiant en pied des seuls badges concernés.
11.5 Les cas qui cassent la mise en page
Le badge est un cadre utile de 89 × 132 mm — 105 et 148 moins deux fois la zone de silence. Ce qu'on y met est variable. Le logiciel fixe donc un ordre de sacrifice, écrit une fois, et ne laisse jamais la mise en page se résoudre par un débordement.
nom › lignes de tour › appartenance › nom de l'événement
- Un nom très long. Le nom ne s'abrège jamais. Le logiciel réduit le corps par paliers jusqu'à un plancher de lisibilité mesuré sur les métriques de la police embarquée, puis replie sur une seconde ligne, puis sur une troisième. Au-delà, il retire l'appartenance, et le badge reste juste. Le seuil se cale sur le glyphe le plus large de l'alphabet visé, jamais sur une moyenne (§ 14.1) : un nom en capitales larges déborde sinon par-dessus la première ligne de tour, en silence.
- Une appartenance très longue. Elle s'abrège avec une marque, sur une seule ligne. C'est une étiquette de contexte, pas une identité, et un repliement sur trois lignes mange les tours.
- Beaucoup de tours. Le logiciel passe à deux colonnes au-dessus d'un nombre de tours mesuré sur le gabarit, et n'affiche jamais une liste tronquée : un badge qui omet le tour 5 envoie son porteur nulle part. Si la liste ne tient pas à deux colonnes au corps plancher, le logiciel refuse de produire la planche et nomme le nombre de tours en cause. Le gabarit tient largement les deux à six tours d'une soirée ordinaire ; ce refus est une garde contre la troncature silencieuse, pas un comportement attendu.
- Un participant sans appartenance. La ligne n'existe pas. Ni espace réservé, ni tiret : le « — » signale une mesure vide dans la page de qualité (§ 5.4), et sur un carton il se lit comme une donnée manquante dont la personne viendra demander la cause à l'accueil. La place libérée revient au bloc du nom. La mise en page est donc un flux, non une grille de cases fixes.
- Une personne en réserve à un tour. La ligne porte « pas de table » en toutes lettres. Une ligne blanche se lit comme un défaut d'impression.
- Une personne exclue n'a pas de badge (§ 4). Le logiciel annonce le compte produit et le compte attendu — « 257 badges pour 260 participants, 3 exclus » — sur la feuille de contrôle, pour que l'écart soit lu avant la coupe et non à l'accueil.
11.6 L'ordre des badges, et l'imposition
L'ordre est alphabétique, sur la forme normalisée du § 10.1 — espaces réduits, casse et accents ignorés — appliquée au couple (nom, prénom), et départagée par identifiant entier pour que deux productions de la même planche soient identiques.
La comparaison n'emploie pas localeCompare : le § 15.5 l'interdit pour la
même raison qu'ici, l'ordre dépendrait de la langue du système et deux machines
produiraient deux piles différentes du même fichier. Le logiciel compare les
formes normalisées par points de code.
La justification de l'ordre est le geste réel : à l'accueil, une personne arrive et dit son nom. L'hôte cherche dans une pile. Un classement par table exigerait de connaître la table avant de connaître la personne, c'est-à-dire de disposer déjà de la réponse que le badge apporte. Le classement par table sert un autre geste — déposer les cartons sur les tables avant l'arrivée — que le logiciel offre en second choix et ne retient pas par défaut.
L'ordre sur la feuille dépend de la façon de couper, et le logiciel le demande. C'est la différence entre une pile prête à l'emploi et des centaines de cartons à intercaler à la main.
| imposition | ce que le logiciel écrit sur la planche i (sur S planches, P cellules par planche) | pour qui |
|---|---|---|
| feuille par feuille (défaut) | les badges P(i−1)+1 à Pi, en ordre de lecture | ciseaux ou massicot à main : l'opérateur empile les cartons d'une feuille, puis la feuille suivante par-dessous, et la pile sort triée |
| en pile | en position p, le badge (p−1)×S + i | massicot qui tranche toute la rame : les P piles obtenues s'empilent dans l'ordre 1…P et donnent la séquence complète |
Les cellules excédentaires restent vides, sans filet ni mention. Leur place dépend de l'imposition, et le logiciel ne prétend pas le contraire : en feuille par feuille elles sont au pied de la dernière planche ; en pile, la formule les envoie au bas des dernières piles. Dans les deux cas les blancs tombent à la fin de la pile finale, ce qui est la propriété qui compte.
Les deux impositions produisent la même pile finale par deux gestes différents ; prendre l'une pour l'autre oblige à retrier la totalité. Le logiciel inscrit l'imposition retenue dans le nom du fichier produit, avec le nom de l'événement, le format de papier et l'horodatage du placement, parce que c'est la seule chose qui survit à l'envoi de la planche à quelqu'un d'autre. Le fichier se dépose à côté des fichiers d'événement (§ 8.6), et le logiciel en nomme le chemin.
Produire la planche n'est pas une modification. Elle n'écrit aucun fichier d'événement et n'ajoute aucune entrée d'historique (§ 8.2), donc elle reste disponible en mode lecture et sur un plan bloqué, où la lecture et l'impression restent ouvertes (§ 9).
11.7 Les autres sorties, et ce que porte toute page imprimée
Le logiciel n'en produit aucune en PDF. Elles se lisent à l'écran et chacune
porte une feuille de style d'impression que le navigateur ou l'hôte
electron applique. Une impression mal paginée s'y rattrape en réimprimant une
page ; aucune ne se découpe, aucune ne doit entrer dans une pochette.
| vue | qui s'en sert, quand | ce qui se passe sans elle |
|---|---|---|
| liste d'accueil | l'accueil, du début à la fin de l'arrivée | la pile de badges triée la remplace pour l'arrivée ; elle ne la remplace ni pour « où est assise telle personne ? » posé par un tiers, ni pour un badge perdu en cours de soirée. Le logiciel la conserve : c'est une table de texte, la sortie la moins exposée au risque de pagination, et la plus manipulée |
| feuille par table | l'animateur de table, à chaque changement de tour | chacun connaît sa table par son badge, donc la rotation se fait ; ce qui disparaît est l'appel nominal, et une place vide ne se remarque plus |
| plan de salle | l'opérateur et le personnel, au montage | il reste à l'écran, où il est interactif ; c'est la vue qui perd le plus à n'être pas sur papier pendant le montage d'une salle sans portable |
| rapport de placement | l'opérateur seul, avant la soirée, pour choisir une proposition | il a l'écran sous les yeux au moment où il choisit ; le logiciel exporte en plus les indicateurs en CSV |
| page de qualité | l'opérateur seul, au même moment | idem ; ses graphiques sont en SVG, que le navigateur imprime à la résolution de l'imprimante, et chacun garde le jumeau tabulaire du § 12.7, qui est ce qui s'imprime proprement |
Chaque page imprimée porte un pied de cinq éléments : le nom de l'événement, sa date, le tour, l'horodatage du placement et la version (§ 18.6). Une feuille sans horodatage circule après avoir été invalidée et personne ne peut dire laquelle des deux versions posées sur la table est la bonne. La règle vise la page ; un carton découpé et porté au cou n'est pas une page (§ 11.4).
Un plan qui n'est ni retenu ni bloqué s'imprime avec la mention BROUILLON en filigrane, planche de badges comprise. Le logiciel n'interdit pas d'imprimer un brouillon ; il interdit qu'on le confonde avec le plan définitif.
La pagination du plan n'est pas une affaire de feuille de style. Un navigateur
ne sait pas fragmenter un élément SVG en pages contiguës : une règle de saut
de page porte sur des boîtes, pas sur l'intérieur d'un dessin. Le logiciel
produit donc, pour l'impression, autant d'éléments SVG que de pages, chacun
portant son propre viewBox sur sa tuile et son cartouche de repérage, et la
feuille de style en place un par page. La découpe du plan en pages est une
fonction pure — elle rend N cadrages contigus et leurs cartouches — éprouvée
sous node : c'est du code qu'il faut éprouver, pas une règle de style. Le
logiciel calcule l'échelle qui ferait tenir le plan sur une page ; si la taille
de caractère tombe sous le seuil mesuré de lisibilité, il découpe, le choix
restant offert et le découpage proposé par défaut.
La mention « disposition relative » et l'échelle graphique du § 7.7 restent sur chaque page imprimée du plan. Elles y gagnent même leur pleine utilité : c'est désormais le réglage d'échelle du navigateur, et non le logiciel, qui peut changer le facteur sans prévenir.
Une vue d'aperçu d'impression applique au document vivant la feuille de style d'impression réelle. Ce n'est pas une seconde mise en page : c'est la même, regardée avant d'être envoyée à l'imprimante — là où l'aperçu d'une mise en page PDF en serait une seconde, avec le défaut que les deux dérivent et que les tests des deux restent verts. C'est cette vue que le guide photographie (§ 19.6).
L'export CSV des indicateurs suit les conventions du § 10.3 : UTF-8 avec
marque d'ordre d'octets, séparé par ;, décimale française. Un fichier séparé par
des virgules dont les nombres portent une virgule décimale s'ouvre en colonnes
fausses dans un tableur francophone, et l'opérateur en conclut que le logiciel
compte mal. L'ordre des colonnes et des lignes est fixé : deux exports du même
placement sont identiques octet pour octet, sans quoi les comparer dans un
tableur est un exercice de tri. Il porte en tête une ligne qui dit qu'il n'est
pas un format d'entrée : le CSV des lignes refusées du § 10.1, lui, se réimporte
tel quel, et deux fichiers CSV dans le même logiciel dont l'un se réimporte et
l'autre non se confondent au premier essai.
Fiches participants : la sortie imprimée est supprimée. Son contenu — nom, appartenance, titre, table et siège par tour — est exactement celui du badge. Deux sorties pour la même information divergent ; celle qui tient autour du cou gagne. La fiche à l'écran demeure : le § 12.5 en fait le chemin d'accès garanti à la ligne d'une seule personne quand la matrice n'est pas dessinée, et le § 7.4 la range parmi les documents qui ne tronquent jamais.
11.8 Ce que le logiciel refuse encore
- Produire une planche depuis un plan qui a dérivé. Si les participants ou le mobilier ont changé depuis que le placement a été retenu, le logiciel signale la dérive (§ 9) et nomme ce qui a changé avant de produire. L'horodatage du badge le dirait aussi, mais après la coupe.
- Produire une planche partielle en silence. Rééditer le badge de trois retardataires est une commande nommée, qui produit une planche de trois badges et l'annonce comme telle sur sa feuille de contrôle. Tant que le placement n'a pas bougé, le badge réédité est identique à celui de la première planche — c'est ce qu'on attend d'un badge perdu ; s'il a bougé, l'horodatage diffère, et c'est ce qu'on attend d'un badge refait.
12. La page de qualité
12.1 Les deux questions
L'opérateur n'ouvre pas cette page pour connaître une variance :
- Ce placement est-il bon ? — reste-t-il à gagner en relançant ?
- Qui s'y trouve mal servi ? — car la plainte ne vient pas de la moyenne, elle vient d'une personne.
Le logiciel n'affiche aucune forme qui ne réponde à l'une des deux, et chaque vue porte en toutes lettres la question qu'elle traite — la vue de comparaison du § 12.10.6 comprise : laquelle des deux sert mieux, et qui y perd ?
Le minimum gouverne la page : chaque vue place le cas le plus défavorable à une position fixe et prévisible — le premier point, la première ligne — jamais là où il faut le chercher. La moyenne l'accompagne en encre secondaire et n'apparaît jamais seule. Un placement où chacun rencontre 27 personnes sauf une qui en rencontre 14 a une excellente moyenne et un problème.
12.2 L'en-tête chiffré
Les indicateurs du § 5.4 en une rangée de tuiles, chacun avec son plafond et la mention de la population mesurée. Trois tuiles de tête, dans cet ordre :
| tuile | la question qu'elle pose |
|---|---|
| minimum brut | combien de personnes rencontre la plus mal servie ? |
| manque maximal (§ 12.10.4) | de combien la plus privée est-elle privée, compte tenu de là où ce placement l'a assise ? |
| écart d'itinéraire maximal (§ 12.10.4) | cette proposition a-t-elle perdu avant même de s'asseoir ? |
Le chiffre de tête est une mesure avec son unité et son plafond, jamais un score composite.
12.3 Première vue — le profil trié
Question : qui est le plus mal servi, de combien, et combien sont-ils ?
Une courbe en escalier. En abscisse les participants triés du moins bien au mieux servi — c'est un rang, et l'axe le dit. Deux séries sur un seul axe vertical : personnes distinctes rencontrées, et appartenances distinctes rencontrées.
Un seul axe, parce que le nombre d'appartenances rencontrées ne peut pas dépasser le nombre de personnes rencontrées : la seconde courbe est partout sous la première, et l'écart se lit directement comme la redondance des cercles fréquentés. Deux axes détruiraient cette propriété et fabriqueraient, par le seul choix d'alignement, une corrélation absente des données.
Un seul tri, celui de la première série, vaut pour les deux : une abscisse désigne toujours le même participant. La seconde courbe est donc irrégulière, et cette irrégularité est de l'information.
Les égalités se départagent par une clé stable — sinon deux affichages du même placement donnent deux dessins, et deux impressions ne se superposent pas.
Le plafond réalisé se trace dans le même ordre : règle horizontale quand il est constant, ligne en escalier dès qu'une table est incomplète. Une règle unique mentirait.
Le logiciel vérifie les trois inégalités de la ligne de décomposition du
§ 12.10.5 — N − 1 ≥ plafond a priori ≥ plafond réalisé ≥ rencontres. La garde
est une fonction du moteur,
verifier_indicateurs(mesures, plafonds_a_priori, plafonds_realises) (§ 14.12) :
avec une seule famille de plafonds, l'inégalité a priori ≥ réalisé — la seule
que la bascule vers l'occupation rend non triviale — ne serait éprouvée nulle
part. Si une inégalité tombe, le calcul est faux : la page refuse de dessiner
et le dit, plutôt que d'afficher un graphique impossible que personne ne
regardera d'assez près.
Les places contraintes sont marquées. Sans cela, l'opérateur attribue au générateur un minimum que sa propre réservation impose.
Pourquoi cette forme. L'histogramme fait de la personne la plus mal servie une barre de quelques pixels au bord du dessin, que l'œil saute. La boîte à moustaches remplace le minimum par une convention de tracé que l'opérateur ne parle pas. Le violon lisse précisément le bout de queue qui porte l'information. La courbe triée commence par le pire cas.
Ce qu'on lit quand le placement est mauvais : une falaise à gauche — bon en moyenne, contient une plainte ; une courbe plate mais basse — défaut systémique, relancer a du sens ; les deux courbes largement écartées — les gens rencontrent beaucoup de monde et toujours les mêmes cercles, l'échec exact que la diversité cherche à éviter et qu'aucun chiffre global ne montre.
Cette vue est inchangée pour UNE proposition : le nombre de rencontres s'y lit en absolu, l'escalier du plafond est tracé à côté, et l'opérateur lit l'écart à l'œil. Dès deux propositions, la comparaison change de quantité : elle trace le manque, et non les nombres bruts, pour la raison exposée au § 12.10.1. Quand les courbes se croisent, l'arbitrage est réel — l'un sert mieux les plus mal lotis, l'autre la masse — et le graphique le montre au lieu de le trancher. En comparaison, l'abscisse change de sens et la page l'écrit : le rang 1 de l'une et de l'autre ne désignent pas la même personne.
12.4 Deuxième vue — les collisions d'appartenance
Question : d'où vient le problème, et relancer peut-il y changer quelque chose ?
Des barres horizontales, une par appartenance, la pire en haut, chacune empilée en deux segments : prouvé inévitable (le plancher du diagnostic) et reste. Chaque ligne porte l'effectif du groupe.
La barre est dessinée en collisions cumulées — des occurrences (paire, tour) —, parce que c'est l'unité dans laquelle le plancher se démontre. Empiler deux unités dans une même barre produit un segment « reste » négatif : un plan qui garde les deux mêmes collègues ensemble aux quatre tours mesure 1 paire distincte contre un plancher de 4, la garde du § 12.3 refuse alors de dessiner, et elle a raison — l'arithmétique est fausse, non dans le code mais dans la spécification.
Chaque ligne porte en encre secondaire ses trois autres chiffres : l'effectif du groupe, les paires distinctes, et l'excédent (§ 5.4). Les deux derniers sont ce qui distingue un conflit réparti d'un conflit concentré, et c'est l'excédent qui ordonne les propositions (§ 5.7) : une vue qui ne montrerait que le volume ferait conclure que le plan le mieux classé est le plus mauvais.
Les lignes se trient sur l'excédent, pas sur la longueur de la barre. La pire appartenance est celle qui remet le plus souvent les mêmes personnes ensemble, non celle qui porte le plus d'occurrences — c'est le même arbitrage qu'au § 5.7, et deux tris opposés dans une même page se contredisent devant l'opérateur.
Le second segment ne s'appelle pas « évitable ». Le plancher se calcule en relâchant les contraintes : c'est une borne inférieure, jamais un fait. Nommer « évitable » le complément affirmerait qu'un meilleur placement le retire, et condamnerait l'opérateur à relancer indéfiniment sur un conflit qu'aucun placement ne retire.
Les participants sans appartenance ne forment pas un groupe : sinon un pseudo-groupe de quarante personnes produit la plus longue barre, un plancher entièrement fictif, et l'opérateur conclut que sa salle est trop petite.
C'est la seule vue qui désigne une action : un segment « prouvé inévitable » long signifie que relancer est futile et qu'il faut une table de plus.
12.5 Troisième vue — les paires répétées
La matrice « qui a rencontré qui » n'est pas lisible à 260 participants, et le logiciel ne la dessine pas à cette taille. Les raisons se mesurent : 67 600 cellules donnent environ 4 px par cellule sur un portable, loin sous les 24 px de cible de pointage ; la matrice est creuse à huit neuvièmes ; elle ne porte que R+1 valeurs dont trois sont décisionnelles ; et en ordre alphabétique elle ne montre qu'un grain aléatoire où l'opérateur croira voir un motif.
Ce qui la remplace : un tableau des paires répétées, trié par nombre décroissant — les deux noms, leurs appartenances, le nombre de fois, les tours. Chaque ligne est cliquable et amène sur le plan, au tour fautif.
Deux exceptions : la matrice se dessine sous un seuil de participants mesuré sur le rendu réel, et la ligne d'une seule personne reste toujours accessible par sa fiche à l'écran — chemin garanti vers une personne quand la matrice n'est pas dessinée, et c'est pourquoi la fiche écran demeure alors que sa version imprimée est supprimée (§ 11.7).
Les paires répétées ne sont pas garanties sur la grande démonstration. À 260 personnes, 33 tables et 4 tours, chacun occupe au plus 28 créneaux de voisinage sur 259 autres : rien n'oblige une paire à se répéter, et une épreuve qui affirmerait un tableau non vide pourrait échouer sans qu'aucun défaut n'existe. Le tableau se photographie donc (§ 19.3) sur une configuration construite où le dénombrement le force — 12 personnes, 4 tables de 3, 6 tours : 12 créneaux de voisinage pour 11 autres personnes, donc au moins une répétition pour chacun, quel que soit l'algorithme.
12.6 Les cas limites
Une page de statistiques se casse par ses bords, et chacun se produira.
| cas | ce que la page affiche |
|---|---|
| participants exclus | retirés de toutes les statistiques, effectifs mesuré et exclu annoncés |
| personne en réserve à un tour | gardée, point marqué, avec le nombre de tours non assis ; son plafond réalisé se calcule sur les seuls tours où elle est assise, et son écart d'itinéraire porte ce que son manque ne porte pas |
| aucun placement | le diagnostic préalable et une phrase, jamais des axes vides |
| une seule table | une phrase ; pas trois vues qui disent la même chose |
| un seul tour | le tableau des paires est remplacé par une phrase, non par un tableau vide |
| une seule appartenance, ou aucune | une phrase ; pas une seconde courbe plate à zéro, qui se lit comme une panne |
| aucune collision | la deuxième vue est remplacée par sa phrase |
| noms longs | tronqués à une largeur mesurée, entiers dans l'infobulle et le tableau |
| homonymes | distingués par leur appartenance ; la navigation se fait par identifiant |
| le manque ne prend qu'une ou deux valeurs distinctes | une phrase portant les effectifs, non une courbe à deux marches |
| deux propositions au même profil de manque | « indiscernables sur le manque » ; l'ordre explicite du § 5.7 les départage sur le critère suivant, et la page nomme lequel |
| deux profils qui alternent plusieurs fois | les trois effectifs de rangs, sans rang de bascule ; le jumeau tabulaire porte le détail |
| populations mesurées différentes entre deux propositions — ce que produit la dérive du § 9 sur des générations accumulées | ni courbe ni dominance : la page nomme les personnes exclues de part et d'autre et le dit non comparable |
| le plafond réalisé d'une personne vaut 0 | son manque vaut 0 et n'enseigne rien : la page marque le point et renvoie à l'écart d'itinéraire. Un manque nul n'est jamais lu comme « bien servi » sans son plafond à côté |
| le plafond a priori n'a pas pu être maximisé | l'écart d'itinéraire s'écrit « inconnu », jamais 0 ; le certificat reste hors d'atteinte (§ 5.5) et le départage du § 5.7 qui en dépend est sauté, la page nommant le critère suivant |
12.7 Les règles de forme
- Jamais deux axes verticaux.
- L'identité d'une série ne repose jamais sur la seule couleur : au plus deux séries, étiquetées directement sur le tracé en plus de la légende. La page s'imprime, souvent en noir et blanc.
- Les couleurs d'état sont réservées : la teinte qui signale un conflit ici est la même que sur le plan de salle, et ne sert à rien d'autre.
- Une échelle séquentielle est une seule teinte, du clair au foncé. Jamais d'arc-en-ciel : un dégradé multicolore invente des frontières là où la grandeur est continue.
- Le texte garde une encre neutre ; la couleur est portée par la marque.
- La palette se valide par calcul, jamais à l'œil : simuler d'abord les trois dichromaties, puis mesurer l'écart — mesurer sur les couleurs d'origine ne dit rien de ce que voit une personne daltonienne. 4,5:1 pour le texte, 3:1 pour les marques porteuses d'information.
- Chaque graphique a son jumeau tabulaire : un survol n'est jamais le seul chemin vers une valeur, et un tableau s'imprime. Celui de la vue de comparaison est l'escalier du manque (§ 12.10.6), qui accepte en outre plus de deux propositions là où le tracé s'arrête à deux.
- Le pointage se fait au plus proche : à 260 points, le pas est de quelques pixels, et sans cette couche la vue est inutilisable au pavé tactile.
12.8 Ce que le logiciel ne dessine pas
- Un score unique, une jauge, un feu vert : il moyenne ce que l'opérateur doit arbitrer, cache quel critère a été sacrifié, et éteint la seconde question — un placement à 87 % ne dit pas qui est à 14.
- Un camembert des appartenances : il décrit les entrées, pas la qualité de la sortie ; identique sous un bon et un mauvais placement.
- La moyenne seule, sous quelque forme que ce soit.
- Un nuage « rencontres contre appartenances » : deux entiers bornés, 260 points se superposent sur quelques centaines de positions.
- Deux placements dans deux graphiques aux axes différents.
- Le taux de satisfaction,
Σ rencontres / Σ plafond réalisé: il affiche 100 % sur un placement dont rien ne peut plus être tiré, et 99,8 % sur le placement du § 12.1 où 259 personnes sont à 27 sur 27 et une à 14 sur 27. Deux dixièmes de point séparent un plan sans reproche d'un plan qui contient une plainte. - La somme, ou la moyenne, des manques : une personne à 14 de manque et quatorze personnes à 1 donnent le même total et appellent des décisions opposées. C'est en outre le scalaire qu'une recherche descend le plus volontiers, et il achète toujours la queue contre la masse.
- La moyenne des rencontres, et le taux de couverture des paires qui en est
la même chose divisée par C(N, 2) : quand les tables sont pleines à chaque
tour, le nombre de voisinages créés vaut
R · Σ C(c_t, 2), fixé par le mobilier et identique pour toutes les propositions ; la moyenne est alors une fonction affine décroissante du seul excédent de rencontres, que les colonnes du § 5.4 encadrent déjà par les deux bouts. Sur un seul tour, les répétitions sont impossibles et toutes les propositions ont rigoureusement la même moyenne. - L'écart-type, la variance, l'indice d'équité : minimisés en servant tout le monde également mal. Une courbe plate et basse les emporte sur une courbe haute portant une seule personne en bas — ils inversent l'ordre des deux questions du § 12.1.
- Le rapport manque / plafond : indéfini quand le plafond vaut 0, et il fait passer un manque de 1 sur un plafond de 3 devant un manque de 3 sur 28 (§ 12.10.3).
12.9 D'où viennent les chiffres
Les statistiques sont calculées par le moteur, en fonctions pures, testées
sous node. La page ne recalcule rien : elle lit les mêmes valeurs que le
tableau d'indicateurs et que le classement. Dès que la même arithmétique existe
en deux endroits, le graphique et le tableau finissent par se contredire devant
l'opérateur. La règle vaut pour l'export CSV des indicateurs (§ 11.7) comme
pour le dessin : il lit le même tableau, quel que soit le support.
Les trois vues se dessinent en SVG, avec un module de géométrie frère de celui du plan — même principe, même discipline d'épreuve, mais pas le même module : l'unité du plan est le centimètre réel, celle d'un graphique est le rang et le nombre. Les confondre ferait fuir l'unité du bâtiment dans les axes.
12.10 Le manque : la grandeur qui compare deux propositions
12.10.1 Pourquoi les nombres bruts ne se comparent pas
Le § 12.3 trie les participants sur le nombre de personnes rencontrées. Tant que toutes les tables sont pleines à tous les tours, ce nombre se compare : le plafond réalisé est le même pour tout le monde, et la règle horizontale du plafond suffit à lire l'écart. Dès qu'une table est incomplète — la variante du § 15.1 — le plafond réalisé devient un escalier, et cet escalier change d'une proposition à l'autre : qui passe par la table incomplète dépend du placement.
Comparer les nombres bruts est alors faux dans les deux sens. Une personne à 27 sur un plafond réalisé de 27 est servie au maximum de ce que son placement lui permettait ; une personne à 27 sur 28 est privée d'une rencontre. Les deux affichent 27, et le rang 1 de la courbe désigne la mieux servie des deux.
12.10.2 La grandeur de base : le manque
Pour une personne p de la population mesurée :
manque(p) = plafond réalisé(p) − rencontres(p)
Un entier positif ou nul, exprimé en personnes. Il ramène tout le monde à la même échelle parce qu'il mesure chacun contre ce que son propre placement rendait possible, et non contre une valeur commune qui n'existe pas. Zéro signifie la même chose pour les quatre animateurs des tables de 7 de la grande démonstration, dont le plafond vaut 24, et pour un mobile de quatre tables de 8, dont il vaut 28 : rien de ce placement-ci ne peut leur être repris.
Le plafond réalisé se calcule sur l'occupation, le plafond a priori sur les capacités : le § 5.5 en donne les deux formules et le glossaire.
C'est la lecture que le § 15.1 demande sans la nommer. Le minimum brut de la grande démonstration vaut 24 et aucune relance ne l'améliore jamais ; le manque, lui, répond à la relance. L'opérateur cesse de relancer sur un chiffre qui ne bougera pas.
12.10.3 Le manque relatif : refusé comme grandeur
Le manque absolu ne distingue pas un manque de 1 sur un plafond de 3 d'un manque de 1 sur un plafond de 28. Le logiciel ne fait pourtant pas du rapport manque / plafond une grandeur : ni axe, ni clé de tri, ni tuile d'en-tête, et il ne le calcule nulle part.
| choix | mode de défaillance |
|---|---|
| le rapport gouverne la page | sur un événement à un tour et des tables de 4 — plafond 3 — un manque de 1 vaut 33 % et passe devant un manque de 3 sur 28, qui vaut 11 %. L'opérateur relance pour quelqu'un qui peut gagner une rencontre et ignore trois rencontres perdues. Le rapport est en outre indéfini quand le plafond vaut 0, et ne prend que quatre valeurs sur un plafond de 3 : des paliers que l'œil lit comme des écarts |
| le manque absolu seul | une personne sur un itinéraire bas paraît bien servie alors qu'elle est proportionnellement la plus privée |
Le second défaut se corrige sans inventer de grandeur : le plafond réalisé accompagne le manque partout — tuile, infobulle, tableau — comme le § 12.2 l'exige déjà du chiffre de tête, et à manque égal, le tri place d'abord le plafond le plus bas. Entre deux manques égaux, la personne proportionnellement la plus privée est donc la première ; entre deux manques différents, le tri ne prétend rien sur la proportion, et c'est le choix assumé ci-dessus. L'infobulle d'un point porte la ligne entière, qui enseigne le vocabulaire :
manque 3 · rencontres 24 · plafond réalisé 27 · plafond a priori 28 · 259 au total
12.10.4 Les trois chiffres, et aucun quatrième
Chacun porte sa population mesurée et ses deux lignes secondaires — mobiles, ancrés — comme tout agrégat du § 5.4. Quand la population mesurée est vide, les trois s'écrivent « — ».
1. Le manque maximal. De combien la personne la plus privée est-elle privée, compte tenu de là où ce placement l'a assise ? Le plus grand manque sur la population mesurée. Vaut 0 sur un placement dont personne ne peut plus rien tirer, et le logiciel écrit alors le nombre de personnes mesurées à côté. Vaut 13 sur le placement du § 12.1 où chacun atteint un plafond réalisé de 27 sauf une personne qui ne rencontre que 14. Majorant : le plus grand plafond réalisé, qui borne l'axe. C'est la transposition du « le minimum gouverne la page » sur une échelle comparable ; la page écrit « le plus grand manque », jamais « manque minimal », qui désignerait le mieux servi.
2. L'effectif du manque. La falaise est-elle une personne ou quarante ? Le nombre de personnes dont le manque vaut au moins 1. Vaut 0 sur un placement sans manque — un vrai zéro, mesuré sur une population non vide, et non un « — ». Vaut 1 sur le placement précédent, et jusqu'à l'effectif mesuré sur un placement systématiquement mauvais. Un manque maximal de 13 porté par une personne et un manque maximal de 13 porté par quarante appellent deux décisions opposées : déplacer quelqu'un, ou ajouter une table.
3. L'écart d'itinéraire maximal. Cette proposition a-t-elle perdu avant même
de s'asseoir ? Le plus grand plafond a priori(p) − plafond réalisé(p). Il
recouvre les deux façons de perdre sur le parcours : visiter moins de tables
distinctes qu'il n'était possible, et occuper des tables incomplètes. Vaut 4
dans la variante du § 15.1 pour un mobile que la proposition assoit aux quatre
tours à une table occupée par 7 : quatre tours à 7 − 1 − 1 = 5 sièges
renouvelés, soit 20, plus les 4 animateurs rencontrés une fois — 24 contre les 28
que la configuration promettait.
C'est le chiffre qui sauve le manque d'un contresens. Une personne mise en réserve à trois tours sur quatre a un plafond réalisé minuscule et un manque nul : elle n'apparaît ni au premier chiffre, ni au second, alors qu'elle est la plus mal servie de la soirée. Son écart d'itinéraire, lui, est énorme.
Et c'est une borne, pas une promesse. Un écart d'itinéraire non nul n'est pas toujours récupérable : dans la grande démonstration, les tables de 7 offrent 112 sièges-tours dont 16 aux animateurs, donc 96 sièges-tours mobiles qu'un placement ne peut pas ne pas servir ; chaque mobile n'en absorbant que 4 au plus, au moins 24 mobiles terminent sous leur plafond a priori de 28, et l'écart d'itinéraire maximal ne descend jamais sous 1. Il en va de même dans la variante, où les 4 sièges vides laissent à chaque tour une table d'au moins 3 mobiles incomplète. La page écrit ce plancher à côté du chiffre, comme le diagnostic du § 5.6, et n'appelle jamais « récupérable » le complément — exactement la discipline du § 12.4 sur « prouvé inévitable » et « reste ». Sans ce plancher, le troisième chiffre envoie l'opérateur relancer indéfiniment vers un 0 que la configuration interdit.
12.10.5 La décomposition, et ce qu'elle autorise à écrire
Quatre quantités sur une ligne, trois différences entre elles :
N − 1 ≥ plafond a priori ≥ plafond réalisé ≥ rencontres
écart d'itinéraire manque
Le logiciel ne nomme que deux de ces différences — celles qu'il affiche. La première est le diagnostic du § 5.6, qui a déjà ses mots. Le § 5.5 pose « deux notions, deux noms, jamais trois » ; la discipline tient, parce que aucun de ces deux noms ne désigne une borne : ce sont des différences entre deux termes voisins de la même ligne, et il n'existe toujours que deux bornes.
La décomposition tranche une ambiguïté : le certificat de « minimum atteint » se
lit contre le plafond a priori, donc sur manque + écart d'itinéraire,
jamais sur le seul manque (§ 5.5). Conséquence directe, que le guide d'usage
écrit : la grande démonstration ne décerne jamais ce certificat.
La vérification du § 12.3 — la courbe ne dépasse jamais son plafond, sinon la page refuse de dessiner — devient un test de signe sur les trois inégalités de la ligne. Un écart négatif est un calcul faux, et la page le dit au lieu de tracer.
12.10.6 La vue de comparaison
Elle porte sa question en toutes lettres, comme le § 12.1 l'exige de chaque vue : laquelle des deux sert mieux, et qui y perd ?
Le § 12.3 reste inchangé pour une proposition. Quatre lignes ne se lisent pas ; dès deux propositions, le logiciel dessine donc l'écart lui-même.
- La quantité : le manque, et lui seul. Une seule série par proposition, deux propositions au plus sur le tracé, étiquetées directement (§ 12.7).
- L'axe vertical : le manque en personnes, zéro en bas, graduations entières, borne haute commune aux deux courbes — jamais une échelle par courbe, que le § 12.8 refuse. Le zéro porte son libellé : « rien à reprendre à ce placement ».
- L'abscisse : le rang, chaque proposition triée par son propre manque décroissant, départagé par plafond réalisé croissant puis par identifiant. Les deux courbes descendent. Le rang 1 de l'une et de l'autre ne désigne pas la même personne, et la page l'écrit.
- Le jumeau tabulaire (§ 12.7) est l'escalier du manque : une ligne par valeur de manque, et par proposition une colonne d'effectif et une colonne d'effectif cumulé — la seconde est la courbe elle-même, lue à l'envers. Il s'imprime, et il accepte plus de deux propositions là où le tracé s'arrête à deux. Il lit le même tableau que le tracé (§ 12.9).
Quand aucune ne domine, le § 12.3 dit déjà que l'arbitrage est réel. Le manque change ce qui se lit : avec des nombres bruts, un croisement peut n'être qu'un artefact des plafonds — la courbe du bas appartient peut-être à des gens assis à des tables de 7. Avec le manque, le croisement porte sur le placement. Le logiciel ne laisse pas lire la position du croisement à l'œil sur 260 points : il compte les rangs, parce que deux profils décroissants peuvent se recroiser plusieurs fois et qu'un « rang de croisement » unique n'existe pas en général. La page écrit les trois effectifs — « A sert mieux à 12 rangs, B à 200, égalité à 48 » — et, quand le signe ne change qu'une fois, nomme en plus le rang de bascule.
Ce que la vue ne dit pas, et que l'en-tête garde à côté : le manque mesure chacun contre son propre placement, donc une proposition peut avoir un manque plus bas en offrant moins de rencontres réelles. La tuile du minimum brut et l'écart d'itinéraire maximal restent affichés à côté des courbes ; une implémentation qui les retire rend le piège invisible.
12.10.7 La dominance
Dominance de profil. La proposition A domine B quand, à chaque rang i,
manque_A(i) ≤ manque_B(i), avec au moins une inégalité stricte, les deux séries
étant triées chacune par son propre ordre. C'est exactement « les courbes ne se
croisent pas ». Un parcours linéaire après le tri suffit. Le logiciel l'énonce en
toutes lettres quand elle tient — « la proposition A sert mieux à tous les
rangs » — et n'affiche rien quand elle ne tient pas : un témoin presque
toujours éteint apprend à ne plus être regardé, et la page montre déjà le
croisement.
La formulation est à surveiller mot à mot : « sert mieux à tous les rangs » n'est pas « sert mieux chaque personne ». Le rang 3 des deux propositions désigne deux personnes différentes. Écrire la seconde phrase promet à l'opérateur qu'il peut basculer sans léser personne, ce qui est faux.
Dominance personne par personne — manque_A(p) ≤ manque_B(p) pour tout p —
se calcule aussi, en une passe, et le logiciel ne la calcule pas. La raison
est une implication, pas une statistique : un ordre point par point survit au
tri, donc une dominance personne par personne entraîne toujours la dominance de
profil, qui est déjà annoncée. La seconde ligne ne s'allumerait que dans les cas
où la première est déjà allumée, et l'opérateur n'en tire aucune décision
différente.
La petite démonstration du § 15.3 donne le test qui peut échouer (§ 14.2) : son plan parfait a un manque nul pour les douze, donc aucune proposition ne le domine, et il domine strictement toute proposition qui n'est pas elle-même à manque nul. Le test s'énonce dans ces termes et non en « domine toute autre proposition » : la petite démonstration admet plusieurs plans parfaits, et deux profils tous à zéro ne se dominent pas — la formulation forte ferait échouer le test sur un résultat juste. Le test refuse de passer s'il ne trouve aucune proposition concurrente à comparer.
12.10.8 Ce que le logiciel ne sort pas
Le § 12.8 en porte la liste, allongée de quatre familles qui s'offrent d'elles-mêmes et trompent dans ce contexte précis : le taux de satisfaction, la somme ou la moyenne des manques, la moyenne des rencontres et le taux de couverture des paires, l'écart-type et ses parents. Chacune y est écartée avec le chiffre qui montre sa défaillance.
12.10.9 D'où viennent ces chiffres
Du moteur, en fonctions pures, comme tout le § 12.9. Le manque et l'écart d'itinéraire sont deux soustractions sur des tableaux déjà calculés ; ils n'ouvrent aucun parcours des ensembles de rencontres et ne changent pas le coût du § 5.10. Le tri du profil est en O(N log N) sur une clé stable. La page ne recalcule rien.
Une réserve honnête sur l'un des deux : le manque ne coûte rien, le plafond réalisé se lisant sur le placement. L'écart d'itinéraire suppose le plafond a priori disponible par personne, et non seulement en agrégat au diagnostic ; son coût est exactement le point 4 du § 17, que cette section rend porteur. Tant qu'il n'est pas mesuré, le troisième chiffre s'écrit « inconnu » plutôt que de retarder la page, et le départage du § 5.7 qui en dépend est sauté en nommant le critère appliqué à sa place.
13. Architecture
13.1 Forme du projet
Une application Capacitor, dont le code web vit dans www/ — la sortie de
Vite, que Capacitor désigne comme son répertoire web — et ne dépend d'aucun
service.
| plateforme | ce qui la porte | rôle |
|---|---|---|
electron |
une coquille Electron propre au projet, emballée par electron-builder |
la livraison : un exécutable Windows |
web |
la plateforme web de Capacitor, servie en local | le développement et les tests sous Linux |
android |
la plateforme Android de Capacitor | ouverte, non requise pour la première livraison |
Capacitor ne porte pas l'exécutable Windows, et c'est délibéré. Il ne fournit de plateforme de bureau que par une extension communautaire, et une plateforme dont la mise à jour ne dépend pas du projet fige le moteur d'exécution embarqué : l'exécutable livré porterait un Chromium que plus personne ne corrige. La coquille du projet tient en deux fichiers — le processus principal et le script de préchargement — et fixe elle-même sa version d'Electron, que la construction met à jour comme n'importe quelle dépendance.
La coquille est l'endroit où vivent les deux frontières de plateforme du § 13.4
sous electron : le système de fichiers — écriture atomique, verrou, sonde
d'écriture, chemin publié par le lanceur portable (§ 8.6, § 8.8) — et
l'impression (§ 11.7). Le processus principal les exécute ; le script de
préchargement les expose à l'application par un pont étroit et nommé,
l'isolation de contexte restant active et l'intégration de Node coupée dans la
page. Une page qui atteindrait le système de fichiers directement ferait d'une
faille d'affichage — un nom importé interprété comme du balisage — un accès
complet au disque.
Le code que Vite compile vit sous src/, en modules nommés d'après les
couches du § 13.4 : src/moteur, src/geometrie, src/stockage, src/csv,
src/pdf, src/demo, src/application, src/interface. À la racine du
projet, version.json est l'unique source de la version (§ 18.3) et
src/version.genere.js le module qu'elle engendre — versionné dans le
dépôt, parce qu'une séance de développement lancée sans l'étape de construction
doit afficher une version et non une importation manquante, et contrôlé par
rejeu du script, parce qu'un fichier engendré et oublié affiche une version
périmée avec l'assurance d'une constante.
13.2 Une seule langue, et c'est une exigence
Tout — le moteur, la géométrie, l'interface — est écrit en JavaScript. Aucune partie du calcul n'est dupliquée dans un second langage.
Cette propriété doit être préservée délibérément. Dès que la même arithmétique existe en deux endroits, une suite de tests peut prouver qu'ils s'accordent sans jamais prouver qu'ils ont raison : les deux dérivent ensemble et les tests restent verts. Une seule implémentation rend ce mode de défaillance impossible.
Une exception, nommée, et une seule : le calcul incrémental des indicateurs du § 5.10. Elle tient à une condition écrite — le recalcul complet est la mesure, celle dont sortent les chiffres affichés ; l'incrémental n'existe que dans la boucle de recherche, où il sert de score et ne s'affiche jamais — et à une épreuve qui lie les deux à la fin de chaque recherche, à cinq ordres de grandeur de mouvements (§ 14.10).
13.3 La bibliothèque d'interface : Svelte 5, construit par Vite
Le logiciel n'emploie aucune bibliothèque de dessin SVG : le plan est du SVG écrit à la main, et le déplacement, le zoom et la conversion de coordonnées sont des fonctions pures du module de géométrie.
| critère | ce que Svelte apporte |
|---|---|
| tenir hors ligne | se compile en JavaScript ordinaire ; Vite est une dépendance de développement qui ne survit pas à la construction |
| rester rapide | compile des mises à jour fines, sans arbre virtuel ni mémoïsation à poser à la main |
| se tester sans navigateur | n'impose d'écrire aucun calcul dans un composant ; le composant ne contient que le câblage |
| être joli sans y passer des semaines | un fichier de jetons — couleurs, rayons, ombres, échelle typographique — décliné en deux thèmes |
Les candidates écartées. Vue 3 est le second choix défendable, écarté de peu
sur le poids et sur la discipline qu'exige sa réactivité. React est écarté sur la
mémoïsation à poser à la main. Preact sans étape de construction supprime un gain
plus petit qu'il n'en a l'air et se paie chaque jour en ergonomie ; il reste le
repli nommé. Lit est écarté parce que son intérêt tient au DOM fantôme, qui
cloisonne les styles et rend plus coûteuse la lecture du style calculé qu'exige
le § 14.2. Solid est écarté sur la taille de sa communauté, pour un logiciel
destiné à être repris. Les bibliothèques canvas résolvent un problème que le
logiciel n'a pas. D3 est écarté pour une raison précise : d3-zoom conserve
la transformation dans une propriété accrochée au nœud du DOM, hors du
modèle — or l'historique et la sauvegarde automatique exigent que tout état
persistant vive dans le modèle.
Le poids ne tranche pas : l'application compilée pèse quelques dizaines de kilooctets, un paquet Electron plus de cent millions. L'écart entre la plus légère et la plus lourde des candidates représente moins d'un millième du livrable.
Le logiciel n'emploie pas Tailwind ni aucun outil qui déduit les styles en lisant le code source. Le mode de défaillance est précis : les classes d'état d'une table — conflit, rencontre répétée, siège réservé — se composent à l'exécution à partir de ce que le moteur rend. Un balayage de sources ne les voit pas, les élague, et la table en conflit reste grise pour toujours, sans message d'erreur.
13.4 Les couches
interface le plan, les listes, les formulaires
│
application les états, les commandes, l'historique
│
moteur le placement : pur, sans DOM, sans API de plateforme
│
stockage lecture et écriture de fichiers
Le moteur ne connaît ni le DOM, ni Capacitor, ni Electron. Il reçoit des nombres et des
listes, il rend des placements. C'est ce qui le rend testable sous node, en
quelques secondes — et c'est la condition pour qu'il soit éprouvé sérieusement.
La géométrie du plan est elle aussi un module de fonctions pures, séparé du rendu.
Les deux frontières de plateforme sont le système de fichiers et l'impression : une interface unique, deux implémentations — plus, pour le système de fichiers, une troisième implémentation injectée par les épreuves, qui sait simuler l'échec de renommage du § 8.8 et les cinq issues du § 8.6 : le chemin publié par le lanceur portable, un dossier de travail sous un emplacement de données applicatives, une sonde d'écriture qui échoue, une perte d'inscriptibilité en cours de séance, et un support amovible. Sans elle, les 100 % de branches exigés des chemins de reprise (§ 14.13) ne s'écrivent pas, et la règle à cinq issues du § 8.6 reste le code le moins éprouvé du logiciel alors qu'elle décide de l'endroit où vit la soirée.
La frontière des couches est gardée mécaniquement, et pas seulement par
discipline : un test du graphe d'imports refuse qu'un module de src/moteur
ou de src/geometrie importe de src/interface, de src/application, de
src/stockage ou d'une interface de plateforme. Il refuse de même que
src/stockage importe de src/csv, de src/application, de src/interface ou
d'une plateforme, ou touche le navigateur hors de ses deux implémentations du
système de fichiers, qu'aucun autre de ses modules n'importe ; que src/csv,
qui lit le modèle du stockage, importe de src/application, de src/interface
ou d'une plateforme ; et que src/application importe de src/interface ou de
Svelte. Un type que la documentation d'un module importe compte comme un
import. Le projet node ne chargeant pas le greffon Svelte (§ 14.8), un test de
moteur qui importerait un composant échoue au chargement — mais cette garde-là
ne couvre que les composants, et un module qui touche document ne tomberait
qu'à l'exécution de la branche fautive.
13.5 Ce que le SVG donne et que le canvas ferait payer
- La désignation de ce qui est sous le pointeur, pour la sélection : le navigateur répond exactement, sans une ligne de code. En canvas il faut la réécrire et la maintenir en accord avec le dessin — une divergence donne des clics qui tombent à côté sans que rien ne le signale.
- L'épreuve par le style calculé (§ 14.2). Sur un canvas il n'y a pas de style calculé : on en serait réduit à comparer des pixels.
- Le contraste et les deux thèmes : en SVG les couleurs sont des propriétés CSS, gouvernées par un seul fichier de jetons.
- L'impression vectorielle : le plan s'imprime en SVG, à la résolution de l'imprimante (§ 11.7), là où un canvas exigerait un second chemin de rendu à haute densité.
- L'accessibilité :
tabindex,roleetaria-labelfonctionnent sur les éléments SVG. Un canvas est un rectangle opaque pour un lecteur d'écran. - L'inspection : le plan s'ouvre dans les outils du navigateur, élément par élément.
Ce que le canvas gagnerait — des dizaines de milliers d'objets — ne se présente pas : une salle plafonne à quelques dizaines de tables.
14. Exigences de développement
Chacune répond à une façon connue de se tromper.
14.1 Les constantes de géométrie se mesurent
Toute dimension qui dépend du rendu du texte se mesure dans un navigateur, sur la police réellement rendue. Jamais déduite, jamais posée de tête.
Un seuil en nombre de caractères se cale sur le glyphe le plus large de l'alphabet visé, pas sur une moyenne. Un seuil moyen laisse déborder les noms écrits en lettres larges, silencieusement, par-dessus la ligne suivante.
Chaque constante mesurée porte en commentaire ce qu'elle mesure, pourquoi, et sur quelle configuration elle a été relevée, afin que personne ne la « corrige » plus tard par le calcul.
Chaque seuil dit « mesuré » a un propriétaire nommé — le banc de mesure du
§ 19.10, le niveau node, ou le niveau manuel. Un seuil sans propriétaire
reste à sa valeur de départ pour toujours. Deux mesures sortent du banc parce
qu'un banc de navigateur ne les prend pas : le décalage des imprimantes
visées et leur marge non imprimable, qui bornent séparément la zone de
silence du badge sur un bord de coupe et sur un bord de feuille (§ 11.3). Elles
appartiennent au niveau manuel, avant livraison ; tant qu'elles n'ont pas eu
lieu, la constante déclare en commentaire qu'elle est un défaut de travail.
14.2 Un test qui ne peut pas échouer ne prouve rien
- Tout test ajouté doit être montré en train d'échouer sur le code d'avant.
- Un test de rendu lit le style calculé, pas la présence d'une classe : une classe peut se poser sur un élément qui ne peint rien.
- Un test qui parcourt un ensemble refuse de passer quand cet ensemble est vide : « zéro fichier examiné » n'est pas « zéro problème ».
La clause de l'ensemble vide ne vaut pas que pour les balayages d'arborescence. Elle vaut pour le vérificateur d'invariants (§ 14.12) — un vérificateur qui rend toujours la liste vide rend verts tous les tests de propriété qui s'appuient sur lui ; pour les boucles de propriété, qui comptent les configurations acceptées et échouent sous un plancher, parce qu'un générateur refusant les configurations difficiles rend la propriété vraie sans rien éprouver ; et pour le choix des comptes d'épreuve. Un accord incrémental éprouvé à dix mouvements, ou une pagination éprouvée sur le seul compte où la troncature et l'arrondi par excès coïncident, sont des tests qui ne peuvent pas échouer.
14.3 Les règles se gardent, pas seulement les cas
Quand un défaut est corrigé, le test qui l'accompagne épingle la règle plutôt que le cas. Une valeur remise à la main sera défaite par la main suivante ; une règle qui fait échouer la construction tient.
14.4 Les niveaux d'épreuve
| niveau | ce qu'il couvre | quand |
|---|---|---|
node, série surveillée |
moteur, géométrie, CSV, indicateurs et plafonds — des fonctions pures ; cohérence des versions (§ 18.5) ; appariement captures/manifeste (§ 19.7) ; aller-retour et canonicité du stockage sur la petite démonstration ; une production de planche de badges avec contrôle de géométrie et de couche de texte ; grille de propriétés sur petites instances | à chaque modification, quelques secondes (§ 14.14) |
node:long |
accord de l'incrémental à 1, 10, 100, 1 000 et 10 000 mouvements ; rejeu du générateur et empreinte ; pagination des badges sur ses cinq comptes A4, ses quatre comptes Lettre et ses deux impositions ; reconstruction de tous les instants du journal ; aller-retour sur la grande démonstration ; coût d'enregistrement ; sonde d'écriture sur système de fichiers réel ; grande grille de propriétés | avant un commit, et à la construction |
| navigateur | ce que l'interface dessine, ce que les gestes produisent, les coordonnées ; le scénario de pilotage (§ 19.3) ; le banc de mesure (§ 19.10) ; les épreuves de style calculé | avant chaque livraison |
| manuel | une soirée complète, de la saisie à l'impression ; le décalage des imprimantes (§ 11.3) ; pointercancel et touch-action (§ 7.3) |
avant chaque livraison au client |
| sa propre commande | l'audit par mutation (§ 14.13) | avant une livraison, sous aucun budget |
La commande surveillée ne lance que le projet node. Le lanceur en porte
deux (§ 14.8) ; lancés ensemble, ils démarrent un navigateur à chaque
modification, et le niveau navigateur reste à « avant chaque livraison ».
Le partage entre les deux séries node est écrit avant la première mesure,
et non après. Trois familles tombent naturellement dans la série surveillée sans
que personne ne les compte — la production de PDF, qui charge un
sous-ensemble de police et analyse sa sortie ; les épreuves de stockage à
système de fichiers réel ; la grille de propriétés, qui appelle un générateur
capable de construire 260 participants et un tour témoin. Chacune a l'ordre de
grandeur que le § 14.14 désigne comme fatal, et la réponse naturelle au
dépassement — relever le budget — supprime la propriété qu'il achète.
jsdom ne sait pas éprouver la géométrie SVG : il n'implémente ni getBBox,
ni getScreenCTM, ni la mise en page du texte. Tout ce qui touche à une
dimension réelle exige un vrai moteur de rendu. Croire l'inverse fait sauter
l'étage intermédiaire et laisse partir les défauts de coordonnées.
14.5 Accessibilité et thèmes
Le texte tient 4,5:1 de contraste, les marques porteuses d'information 3:1. Chaque couleur est accompagnée du ratio mesuré sur la couleur relue du rendu, non sur le jeton source. Les deux thèmes sont éprouvés par rendu, jamais seulement par calcul, et le tableau des ratios est engendré pour les deux à partir des styles relevés au pilotage (§ 19.5), jamais recopié.
Le logiciel n'applique aucune opacité partielle, aucun filtre et aucun mode de fusion au texte ni aux marques porteuses d'information. Le style calculé rend la couleur résolue d'un élément, non le pixel composé : sous une opacité partielle, la couleur relue diffère de la couleur peinte et le tableau des ratios mentirait avec l'autorité d'une mesure. Sans opacité ni filtre, les deux coïncident par construction.
14.6 La traduction, posée dès le départ
Une seule langue est livrée, mais aucune chaîne visible n'est écrite en dur dans un composant. Le coût est nul aujourd'hui ; le retrouver plus tard suppose de relire toute l'interface.
La règle se garde par deux moyens, et aucun n'est le scénario de captures
(§ 19.11) : comparer le texte affiché au texte attendu ne distingue pas une
chaîne venue de la table de traduction d'une chaîne écrite dans le composant —
les deux s'affichent à l'identique. Les deux gardes sont un balayage des
sources sous node, qui refuse une chaîne visible en dur et échoue sur un
balayage vide (§ 14.2), et une exécution en langue témoin où chaque chaîne
traduite est décorée d'une marque : tout ce qui apparaît à l'écran sans sa marque
est écrit en dur.
14.7 Le déterminisme
Aucune source non reproductible — Math.random, Date.now, performance.now,
crypto.getRandomValues — dans le moteur, dans le générateur de
démonstrations, ni dans le stockage et l'analyseur CSV, qui reçoivent
l'horloge et l'aléa en paramètre. Un test de l'arborescence le refuse, et
échoue si son balayage ne trouve aucun fichier. Son périmètre est nommé :
src/moteur, src/demo, src/stockage et src/csv, à l'exclusion du code
d'épreuve, où un tirage sert légitimement (§ 14.12). L'interdit ne porte pas sur
le reste de l'application : l'application lit l'horloge pour horodater les
entrées de l'historique, que le stockage écrit telles qu'il les reçoit, et le
banc de mesure du § 19.10 relève des temps par image.
Ce n'est pas une règle d'hygiène. L'interdit de performance.now dans le
moteur est ce qui rend vraie la phrase « à graine et entrée égales, placement
identique » : une recherche bornée par une durée rendrait le placement dépendant
de la vitesse de la machine, et le § 19.4 neutraliserait alors toutes ses sources
de variation sauf la seule qui compte. La règle d'arrêt de la recherche est pour
cette raison un compte, jamais une durée (§ 5.10).
La version est une constante engendrée à la construction, jamais une lecture d'horloge à l'exécution (§ 18.5) : le moteur et le générateur ne la lisent pas.
14.8 L'outillage des épreuves
Un seul lanceur, deux projets : Vitest. Le niveau node et le niveau
navigateur du § 14.4 sont deux configurations du même lanceur, pas deux outils.
| projet | environnement | ce qu'il inclut | greffon Svelte |
|---|---|---|---|
node |
node, aucun DOM |
src/moteur, src/geometrie, src/stockage, src/csv, src/demo, src/pdf |
absent |
navigateur |
un vrai moteur de rendu, piloté par le mode navigateur de Vitest | src/interface, les épreuves de coordonnées et de style calculé |
présent |
Pourquoi Vitest. Il lit la configuration Vite qui construit le livrable : un module importé par un test se résout exactement comme un module importé par l'application. Deux chaînes de résolution — l'une pour construire, l'autre pour éprouver — sont le mode de défaillance du § 13.2 transposé à l'outillage : le test passe sur un fichier que l'application ne charge jamais, un alias diverge, et rien ne le signale. Le lanceur observe en outre le graphe de dépendances, donc une modification du moteur ne rejoue que les tests du moteur, et son mode navigateur offre la même API d'assertion au niveau 2, de sorte que l'oracle de géométrie du § 7.2 s'écrit une fois.
Les trois durées sont des cibles, mesurées la première semaine. Le démarrage à froid, la relance sous surveillance et la série complète se mesurent sur un squelette, au même titre que la publication du dossier d'origine par le lanceur portable (§ 8.6) et la construction depuis Linux (§ 16). Ce qui est tranché ici n'est pas le chiffre mais lequel gouverne : le démarrage à froid se paie une fois par séance, la relance sous surveillance se paie à chaque geste, et c'est elle que le budget du § 14.14 borne.
L'issue de secours est une règle d'écriture, et elle passe par un module. Les
tests du projet node n'emploient que les assertions de node:assert/strict —
jamais expect, jamais un comparateur propre à Vitest. Ils importent test et
describe d'un unique module local, test/lanceur.js, qui réexporte ceux du
lanceur en place. Les fonctions de node:test ne sont pas globales et celles de
Vitest ne portent pas le même chemin d'import : sans cette indirection, « tourner tels quels sous node --test » est
faux, et le repli exigerait de retoucher chaque fichier le jour où il presse.
Deux conditions le rendent réel et sont tenues par le projet node : ses modules
s'importent par chemins relatifs à extension explicite, sans alias ni syntaxe
propre à Vite. Le prix est réel — les écarts affichés par
assert.deepEqual sont plus pauvres — et il s'accepte parce qu'une dépendance
qu'on ne peut pas quitter finit par dicter l'architecture.
Ce qui est écarté, et pourquoi.
| écarté | raison |
|---|---|
| Jest | exige sa propre chaîne de transformation, parallèle à celle de Vite — la seconde résolution que l'on veut éviter |
| Mocha + Chai + Sinon + nyc | quatre choix à maintenir et à accorder pour ce qu'un lanceur rend d'emblée |
| Karma, Jasmine, QUnit | conçus pour un monde sans étape de construction ; ils rajoutent un second serveur devant Vite |
node --test seul |
ne résout ni les alias de Vite ni un composant ; pas de graphe de dépendances sous surveillance. Reste le repli nommé, et c'est pour lui que la règle d'assertion et test/lanceur.js existent |
| jsdom, sous quelque lanceur que ce soit | le § 14.4 l'écarte déjà pour la géométrie ; elle l'est aussi pour le style — sans mise en page, le style calculé ne rend pas ce qui est peint, et l'épreuve du § 14.2 devient une formalité toujours verte |
| un pilote de navigateur distinct pour le niveau 2 | deux pilotes signifient deux façons de décrire un geste de pointeur, et le § 7.3 est précisément l'endroit où cette divergence coûte cher |
Pour les composants, aucun outil supplémentaire : le projet navigateur
monte le composant dans un vrai moteur de rendu. Un composant ne contient que du
câblage (§ 13.3) ; ce qu'il y a à vérifier est ce qui est peint.
14.9 Où vivent les tests
Le test vit à côté du module — plafond.js et plafond.test.js dans le même
répertoire. Un arbre miroir éloigné ne s'ouvre pas quand on modifie le module, et
le test qui n'est pas sous les yeux n'est pas étendu. Les tests du niveau
navigateur portent le suffixe .navigateur.test.js, qui est aussi ce qui les
range dans leur projet.
Les données d'épreuve vivent dans test/fixtures/, les fichiers d'exemple livrés
restant ceux du § 10.3 — une donnée d'épreuve qui double un fichier livré dérive
de lui. Les noms inventés d'une donnée d'épreuve sont disjoints du réservoir du
générateur : le balayage du § 15.6 refuse qu'un fragment forgé apparaisse hors
des listes du générateur, des fichiers de démonstration et des CSV d'exemple, et
une donnée d'épreuve qui emprunte une racine d'organisation plausible le fait
échouer. Un test vérifie cette disjonction, faute de quoi la garde du § 15.6
tombe au premier exemple écrit de bonne foi.
La co-localisation se garde. Un test de la construction affirme que le paquet
livré ne contient aucun fichier de test ni aucun import du lanceur. Le § 14.15
pose que la suite n'est pas livrée ; avec des tests rangés dans src/, cette
propriété tient par la seule analyse statique de Vite, c'est-à-dire par
chance, jusqu'au jour où un module de production importe une
donnée d'épreuve « pour un cas par défaut ».
14.10 La carte de ce qui s'éprouve
| module (§ 13.4) | niveau | ce qui s'y éprouve |
|---|---|---|
| moteur | node |
indicateurs, plafonds et borne, diagnostic, invariants d'un plan, accord de l'incrémental et du recalcul |
| géométrie | node + navigateur |
conversions, ajustement, bornes de zoom, désignation d'un siège ; l'oracle du moteur de rendu au niveau 2 ; la découpe du plan en pages (§ 11.7) |
| stockage | node |
sérialisation canonique, équivalence geste/entrée/état, reprise après panne, dérivation des noms de fichiers, forme positionnelle (§ 8.9) |
| analyseur CSV | node |
encodage, séparateur, association des colonnes, réconciliation des appartenances, lignes refusées, aller-retour |
| générateur de démonstrations | node |
vecteur du générateur pseudo-aléatoire, empreinte des fichiers livrés, refus, noms forgés |
node |
couche de texte, pagination, police embarquée et table ToUnicode, géométrie de la planche, reproductibilité |
|
| interface | navigateur | ce que les gestes produisent, le style calculé, les trois invariants du § 7.2 |
Le moteur
Un tour où une table est vide ne produit aucune rencontre. Un plan dont une table n'accueille personne au tour 3, et une autre où elle accueille une seule personne : dans les deux cas, zéro paire ajoutée. Il refuse l'énumération qui compte la paire (p, p) et crédite un convive isolé d'une rencontre avec lui-même — défaut qui ne se voit sur aucun total, parce qu'il ajoute un au bon endroit.
Le plafond d'un ancré sur quatre tables de 5, un ancré chacune, cinq tours, vaut
16 ; celui d'un mobile de la même instance vaut 19. Les deux valeurs sont dans
le même test, parce que c'est leur écart qui est l'objet. Il refuse la suppression
du terme min(n_p, …), qui se lit comme une borne redondante et dont l'absence
porte l'ancré à 19, affichant à perpétuité un animateur « à 84 % de son plafond »
(§ 5.5).
Les retours imposés sont identiques pour toutes les propositions d'une même configuration. Sur la grande démonstration, chacune annonce 99 retours imposés. Ce n'est pas le seul chiffre qu'elles partagent, mais c'est le seul qui, additionné aux retours choisis, noierait une quantité variable de quelques unités dans une constante de 99, à une résolution où l'opérateur ne lit plus l'écart. Le test affirme les deux comptes séparés et chacun à sa valeur (§ 5.4).
Une population vide rend « — », jamais zéro. Il refuse somme / 0 affiché en
0, et refuse un minimum d'ensemble vide rendu en Infinity que la mise en forme
convertit en tiret par accident plutôt que par décision.
L'appartenance d'un ancré voyage dans l'instance réduite. Une table porte un ancré d'une appartenance inventée, un mobile de la même appartenance existe, la séparation est active : le plan rendu signale la collision ou l'évite, il ne l'ignore pas. Il refuse la réduction qui n'annote pas les tables, laquelle assoit un invité à côté d'un collègue animateur aux quatre tours sans qu'aucun indicateur ne s'en aperçoive (§ 5.2).
L'incrémental et le recalcul complet s'accordent, et le test dit à partir de
quand ils cessent de s'accorder. Sur chaque configuration de démonstration, une
suite déterministe d'échanges, avec comparaison terme à terme de A(p) et de |F(p)|
à 1, 10, 100, 1 000 et 10 000 mouvements — non au seul terme. Il refuse la mise
à jour qui oublie de décrémenter un compteur sur la table quittée. Le nombre de
mouvements est l'exigence du test : un test à dix mouvements est le test qui ne
peut pas échouer du § 14.2. Cette épreuve est dans la série node:long.
La géométrie
Le plan vide donne k_ajusté = 1 px/cm, pas Infinity. Il refuse la division
par zéro du § 7.2, que le transform accepte sans rien dessiner et sans rien
signaler.
La borne basse du zoom est relative, et le test le prouve en chiffres. Sur une
fenêtre de 1 366 × 700 et un plan de 36 m, k_ajusté ≈ 0,19 px/cm : le test
affirme k_min < 0,2, et affirme k_min = 0,5 × k_ajusté sur deux plans
d'encombrements très différents. Affirmer k_min ≤ k_ajusté serait inutile : avec
un facteur de 0,5 la relation est vraie par construction, y compris si un plancher
absolu a été ajouté — c'est le test qui ne peut pas échouer du § 14.2. Ce qui doit
échouer, c'est le plancher absolu de 0,2.
Le siège désigné est celui que le dessin a posé, dans les deux régimes de zoom. Le test demande au module la position de chaque chaise, puis lui redemande quel siège contient ce point ; puis vérifie qu'un point du vide entre deux chaises ne désigne rien. Il le refait sous le seuil du § 7.3, où les sièges cessent d'être des cibles : sans ce second passage, le basculement n'est éprouvé nulle part.
La découpe du plan en pages est une fonction pure (§ 11.7) : elle rend N
cadrages contigus et leurs cartouches, et s'éprouve sous node. Un navigateur ne
sait pas fragmenter un SVG ; c'est du code qu'il faut éprouver, pas une règle de
style.
Au niveau navigateur, la fonction pure est confrontée à
getScreenCTM().inverse() sur un rendu réel, à plusieurs k et après défilement
de la page. Les trois invariants du § 7.2 ont chacun leur test : que le <svg>
racine ne porte pas d'attribut viewBox ; que son style calculé donne une bordure
et un remplissage intérieur nuls — getBoundingClientRect les inclut, donc
l'origine du contenu s'en décale ; qu'aucun ancêtre, ni lui-même, ne porte de
transformation CSS. Il refuse la feuille de style qui ajoute un jour
transform: translateZ(0) « pour la fluidité » : le plan répondrait faux partout,
et la cause serait cherchée dans le module de géométrie.
Le stockage
Le système de fichiers est un paramètre, pas un import (§ 13.4). Sans l'implémentation injectée, ni le test du renommage ni les 100 % de branches du § 14.13 ne s'écrivent, et les chemins de reprise restent le code le moins éprouvé du logiciel alors qu'ils sont ceux qui perdent une soirée.
Deux sérialisations du même état sont identiques octet pour octet, y compris quand l'état a été construit dans deux ordres d'insertion différents, et y compris quand les sièges ne sont pas attribués — c'est là que la règle du tri croissant du § 8.9 s'éprouve. Il refuse l'ordre de clés hérité de l'itération d'objet et l'horodatage glissé dans la charge utile.
Chaque instant du journal se reconstruit. Après une suite de commandes
dépassant deux frontières d'instantané, le test reconstruit tous les instants
et compare chacun à l'état écrit à ce moment. Il refuse l'application de correctifs
juste quarante-neuf fois et fausse au passage de l'instantané — défaut qui
n'apparaît qu'au bout d'une longue séance, c'est-à-dire la veille de l'événement.
Série node:long.
Un journal en avance d'une entrée se rattrape. Le test écrit la ligne de journal et n'écrit pas le fichier d'état : l'ouverture compare les révisions et retrouve le geste. Il refuse la reprise fondée sur la date de modification des fichiers, que recopier un dossier suffit à fausser.
Une ligne illisible arrête la lecture et annonce le compte. Journal de cent entrées corrompu à la trente-septième : trente-six entrées retenues, soixante-quatre annoncées écartées.
La dérivation des noms refuse con comme CON, et borne le plus long
chemin que l'événement écrit, corbeille et écriture atomique comprises (§ 8.6) :
le test recompose ce chemin en entier, et échoue sur une borne posée sur
.gtt.json, neuf caractères, sur le seul .gtt-journal.jsonl, dix-huit, ou qui
oublie la corbeille ou l'écriture atomique. Elle reçoit la racine en paramètre et
son test l'exerce sur les deux dossiers de travail possibles (§ 8.6).
Un renommage atomique qui échoue ne détruit pas la cible. L'implémentation d'épreuve échoue au renommage : la cible conserve son contenu antérieur, et le logiciel nomme le fichier. Il refuse l'écriture qui tronque la cible avant d'écrire, c'est-à-dire la variante qui perd une soirée quand un agent de synchronisation tient le fichier ouvert.
La forme positionnelle du § 8.9 est abîmée délibérément — une longueur fausse, un identifiant dupliqué, un identifiant inconnu, un identifiant manquant sans entrée de réserve — et le logiciel doit nommer chacune et écarter la seule proposition fautive. Un second test périme délibérément : il exclut une personne, supprime une table, réduit une capacité, et exige que les propositions touchées soient signalées comme dérive et conservées, jamais écartées.
Le coût d'un enregistrement complet se mesure sur la grande démonstration et
échoue au-delà du seuil du § 8.2. Il écrit sur un système de fichiers réel : série
node:long, comme la sonde d'écriture du § 8.6, qui ne s'éprouve utilement
contre un système de fichiers réel qu'au moins une fois.
L'analyseur CSV
Un fichier séparé par des virgules n'est pas lu comme séparé par
point-virgule. Le test affirme que le candidat ; est écarté parce qu'il donne
un champ. Il refuse la règle fondée sur la seule constance, qui importerait
chaque ligne entière dans la colonne nom (§ 10.1).
Un contenu latin encodé en UTF-16 est refusé globalement, avec son remède. Il refuse l'ordre à trois temps, dont le mode de défaillance est muet.
Le collage en bloc et l'import du même contenu rendent les mêmes participants. Le test fournit le même texte décodé par les deux chemins : un collage ne porte pas d'octets, et les quatre temps de détection d'encodage ne concernent que l'import. Il refuse le second analyseur « simplifié » du § 10.2.
Le rapport d'import nomme chaque groupe d'orthographes fondues. L'assertion porte sur le rapport, pas seulement sur le résultat fusionné. Il refuse la fusion silencieuse, qui gonfle G, gonfle A_max avec lui, et qu'aucun indicateur ne signale.
Une valeur d'exclu hors de la liste fermée refuse la ligne, et la ligne revient
dans le CSV des refus, lequel se réimporte tel quel. Le test boucle : import,
export des refus, correction d'un champ, réimport. Il refuse le refus global sur
une faute unique, et refuse le fichier de refus qu'il faudrait nettoyer à la main.
Le générateur de démonstrations
Le vecteur du générateur pseudo-aléatoire est rejoué, et il vient de la publication de référence de l'algorithme. Un vecteur produit en exécutant l'implémentation ne prouve rien d'autre que sa stabilité : il fige la transition, y compris fausse. Pris hors du code, il arbitre. Si l'algorithme retenu ne publie pas de vecteur, le test change de nature et le dit : il devient un témoin de non-régression, pas un oracle (§ 14.11).
Le profil d'appartenances attendu est lu dans le fichier livré, jamais écrit à la main (§ 15.2).
Le générateur refuse quatre configurations, chacune son test : zéro participant, zéro table, une graine dont aucun tour témoin ne se construit, un compte de groupes dépassant le réservoir de noms.
La petite démonstration atteint 8 pour les douze participants, zéro paire répétée, zéro collision, et le logiciel écrit « minimum atteint ». Le test mesure d'abord le plan écrit au § 15.3 — il éprouve les indicateurs contre un oracle — puis lance la recherche et affirme qu'elle atteint le même plafond, sans jamais comparer le plan obtenu au plan écrit (§ 15.5, point 6).
La variante 5, 4, 3 annonce au moins quatre collisions cumulées, et au moins une paire distincte. Les deux chiffres sont différents et le test les sépare : cinq personnes ne tiennent pas sur quatre tables sans que deux se retrouvent, donc au moins une collision par tour, quatre sur les quatre tours — mais l'indicateur « paires de même appartenance » compte des paires distinctes, et un plan qui garde les deux mêmes personnes ensemble aux quatre tours n'en affiche qu'une. Affirmer « aucune proposition ne descend sous quatre » sur cet indicateur ferait échouer le test sur un plan légitime. Un second test tient l'autre bout : le classement par défaut du § 5.7 préfère le plan qui répartit, donc celui dont l'excédent est nul et qui porte quatre paires distinctes, et non celui qui concentre ses quatre collisions sur une seule paire. Le test compare deux plans construits à la main — l'un réparti, l'autre concentré — et affirme l'ordre ; il est l'unique garde contre une réécriture du classement qui le remettrait à l'envers (§ 5.4).
Le PDF
Un nom portant « Ôjgq » et « Æ » se retrouve caractère par caractère dans la
couche de texte. Il refuse le sous-ensemble de police qui laisse tomber un glyphe
en silence, et la police sans table ToUnicode, dont les accents s'impriment juste
et s'extraient en charabia (§ 11.1).
La géométrie du quatre-par-page est contrainte, et le test l'affirme telle qu'elle est : quatre cellules de 105 × 148 mm exactement, trois filets de coupe — une verticale à 105 mm, une horizontale à 148 mm, une horizontale à 296 mm qui retire la bande perdue —, et aucun contenu de badge à moins de la zone de silence d'un axe de coupe ou d'un bord de feuille. Ce ne sont pas deux médianes : 148 n'est pas la médiane de 297.
La pagination s'éprouve sur les comptes qui séparent les deux arrondis, et sur
les deux impositions : n = 1, 4, 5, 259, 261 en A4 — jamais 260, où 260 / 4
tombe juste et où un calcul qui tronque rend la même valeur — et
n = 1, 2, 3, 257 en Lettre. Le compte est celui des participants non exclus.
Série node:long.
Deux exports du même placement produisent la même couche de texte. Il refuse l'horodatage d'export glissé dans le pied de page à la place de celui du placement. L'assertion porte sur la couche de texte et non sur les octets du fichier, que la date de création et l'identifiant de document d'une bibliothèque rendent différents sans que rien ne soit faux.
Le filigrane BROUILLON se lit dans la couche de texte d'un plan ni retenu ni bloqué. Il refuse le filigrane dessiné en image, que personne ne vérifie plus.
Le niveau node est une garde structurelle, à une condition nommée. Le DOM
n'existe pas, donc un générateur de PDF qui lirait l'écran échoue dès que le test
l'exécute — la garde n'est pas l'environnement seul, elle est l'environnement
plus un test qui parcourt le chemin de production ; sans ce test, rien ne lève.
Et elle ne tient que parce que les mesures de texte du badge emploient les
métriques de la police embarquée (§ 11.1) : un appel vivant à
getComputedTextLength ramènerait le DOM dans le module PDF.
14.11 Les oracles, et ce qui n'en est pas
Un oracle est une source de la réponse attendue produite hors du code éprouvé. Un témoin de non-régression est une valeur produite par le code lui-même, figée. Les deux sont utiles, ils ne prouvent pas la même chose, et les confondre fait croire qu'une suite arbitre là où elle ne fait que conserver.
| source | nature | ce qu'elle arbitre |
|---|---|---|
| le plan parfait de la petite démonstration (§ 15.3), vérifié par énumération | oracle | les indicateurs |
getScreenCTM().inverse() du moteur de rendu (§ 7.2) |
oracle | la conversion de coordonnées |
| une énumération exhaustive sur une instance minuscule | oracle | la formule du plafond, le diagnostic |
| le vecteur du générateur pseudo-aléatoire | oracle s'il vient de la publication de l'algorithme, témoin s'il a été produit par l'implémentation | la transition du tirage |
| les fichiers de démonstration livrés et leur empreinte (§ 15.5) | témoin — ils sortent du générateur qu'ils éprouvent | que le générateur n'a pas changé |
Le témoin garde sa valeur entière : le § 15.5 veut que la régénération soit un geste conscient, et un témoin fait exactement cela. Ce qu'il ne fait pas, c'est dire que le générateur a raison.
Ce qu'un oracle apporte qu'une assertion écrite à la main n'apporte pas.
- Il ne partage pas l'erreur de lecture de l'auteur. Une valeur attendue tapée dans un test est calculée par la tête qui a écrit le code, sur la même lecture de la spécification. Si cette lecture est fausse, le test confirme la faute. Une énumération exhaustive sur quatre tables de 5 contredit la formule sans avoir rien lu.
- Il couvre ce que personne ne penserait à écrire. Le moteur de rendu donne la
matrice exacte à tout
k, non aux trois valeurs que l'auteur aurait choisies — et ce sont les valeurs non choisies qui cassent. - Il survit à un changement légitime. Quand la loi de distribution change, le fichier livré dit ce qui a changé ; un effectif écrit dans le test est remis à la main, et la remise à la main est l'endroit où l'on cesse de vérifier.
Un oracle tiré du code éprouvé n'est pas un oracle. L'aller-retour CSV du § 10.2 en est l'exemple : importer puis exporter prouve que les deux moitiés s'accordent, jamais qu'elles ont raison — le mode de défaillance du § 13.2. Il reste utile, et il est toujours accompagné d'au moins une donnée d'épreuve dont le contenu attendu est écrit à la main, ligne par ligne : le fichier de cas limites du § 10.3.
Le recalcul complet est l'oracle de l'incrémental, à la condition du § 13.2 : le recalcul est la mesure, l'incrémental ne vit que dans la boucle de recherche, et le test les lie à la fin de chaque recherche.
14.12 Les invariants et les tests de propriété
Le vérificateur d'invariants est du code de l'application, pas du code de test.
verifier_invariants(plan, configuration) rend la liste des violations :
- chaque participant non exclu est assis à exactement une table par tour ;
- aucun participant n'occupe deux places sur un même tour — distinct du précédent, qui ne dit rien de deux sièges à la même table ;
- aucune table ne dépasse sa capacité, et aucun siège n'est hors de sa table ;
- chaque réservation est honorée, de portée « tour désigné » comme « tous les tours » ;
- aucun participant exclu n'est placé.
Il vit dans le moteur parce que l'application l'appelle : après une génération, après la commande qui complète un placement partiel, après un retour arrière, et au chargement d'un fichier. Un vérificateur appelé seulement par les tests cesse d'être appelé ; appelé par les commandes, il refuse au moment du geste, là où le § 5.9 veut les refus.
La garde du § 12.3 est une seconde fonction, de signature différente.
verifier_indicateurs(mesures, plafonds_a_priori, plafonds_realises) éprouve les
trois inégalités de la ligne de décomposition du § 12.10.5. Elle ne prend pas
un plan mais des mesures, et ce qu'elle refuse n'est pas un geste mais un
dessin. Les deux fonctions vivent dans le moteur, sous la même discipline ; les
confondre obligerait le vérificateur de plan à recevoir des indicateurs qu'il n'a
aucune raison de connaître.
Le vérificateur est lui-même éprouvé par mutation. Le test prend un plan valide, déplace une personne pour qu'elle occupe deux tables au tour 2, et affirme que le vérificateur signale cette violation-là et aucune autre. Une mutation par invariant listé. Sans cela, un vérificateur qui rend toujours la liste vide rend verts tous les tests de propriété qui s'appuient sur lui.
Les tests de propriété valent, et s'écrivent d'abord à la main. Le corpus d'entrées existe déjà : le générateur de démonstrations est déterministe et sait produire une configuration à partir d'une graine. Une boucle sur une grille de graines et de tailles, appelant les deux vérificateurs, couvre l'essentiel sans dépendance nouvelle.
Une bibliothèque de propriétés se justifie à deux endroits, et seulement deux : la formule du plafond et la détection du séparateur. Toutes deux prennent des entrées petites et combinatoires, toutes deux bénéficient du rétrécissement — quand une configuration à 120 participants échoue, l'outil rend un contre-exemple à trois tables, là où la réduction manuelle coûte une heure. Leurs oracles ne sont pas de même nature et le texte ne les confond pas : le plafond s'arbitre par énumération exhaustive ; le séparateur s'arbitre par construction — un tableau de champs engendré, rendu avec un séparateur connu, et la détection doit le retrouver. Ailleurs, non : une propriété sur la recherche rejouerait la partie lente et épuiserait le budget du § 14.14.
La bibliothèque tourne sur une graine fixée. Une bibliothèque de propriétés retire par défaut une graine neuve à chaque exécution : le même code échoue une fois sur dix et passe le reste du temps. Un test capricieux se désactive, et le désactiver coûte plus cher que le défaut qu'il annonçait. La graine est écrite dans le dépôt ; un contre-exemple trouvé devient un test nommé à part entière.
Le piège qu'un test de propriété pose. Un générateur qui refuse les configurations difficiles rend la propriété vraie sans rien éprouver. La boucle compte les configurations acceptées et échoue sous un plancher (§ 14.2).
14.13 La couverture
Aucun seuil global. Un pourcentage d'ensemble se satisfait en couvrant les modules faciles pendant que les branches qui comptent restent nues : le plafond d'un participant partiellement fixé, la frontière d'instantané, le renommage qui échoue. Le chiffre monte, et c'est le chiffre qui cache.
La couverture se mesure et se publie par module, jamais agrégée. Sa valeur est de désigner ce qui n'est jamais exécuté ; c'est une liste à lire, pas une note.
Deux seuils durs, en branches, sur deux modules. Le module des indicateurs et du plafond, et les chemins de reprise du stockage, tiennent 100 % de branches. Ils sont petits, purs — le second à la condition d'injection du § 13.4 — et chaque branche non couverte y est un cas que l'opérateur rencontre au pire moment. Une branche qu'on ne sait pas couvrir est soit du code mort — on le retire — soit un cas que la spécification n'a pas prévu — on l'écrit.
La mesure est sa propre commande, make couverture : la série node sous
instrumentation, hors des budgets du § 14.14. Elle publie un tableau par couche
et par module, et un rapport à parcourir qui montre, ligne à ligne, ce qui n'est
jamais exécuté. Les deux seuils portent sur quatre fichiers —
src/moteur/indicateurs.js et src/moteur/plafond.js, src/stockage/depot.js
et src/stockage/journal.js — et la commande échoue en deçà. Un test de
l'arborescence refuse un seuil abaissé, un seuil global, et un seuil posé sur un
fichier absent ou hors de la mesure : l'outil tiendrait ce dernier pour atteint,
faute de branche à compter. Il refuse de même une commande qui ne mesure pas —
sans instrumentation, la série passe et aucun seuil ne se lit —, une commande
dont les arguments redéfinissent la mesure ou ce qu'elle exécute, un tableau qui
tait les modules pleins, et l'absence du rapport ligne à ligne.
Ce qui l'empêche de devenir une case à cocher ne vient pas de l'outil de couverture :
- La clause du § 14.2 — tout test ajouté est montré en échec sur le code d'avant. Un test qui n'assert rien ne peut pas être montré en échec. Aucun outil ne le vérifie ; l'historique des correctifs, lui, se relit.
- Un audit par mutation, périodique et non bloquant, sur le moteur seul. Un
mutant survivant sur une ligne couverte à 100 % est la preuve directe que le
test exécute sans rien affirmer. Il est sa propre commande, lancée avant une
livraison, avec un résultat écrit — ni dans la série surveillée, ni dans
node:long, parce que son coût est d'un autre ordre que les budgets du § 14.14. Un audit que l'on tenterait de faire tenir dans un budget ne serait pas fait du tout.
14.14 Le budget de temps
| cycle | budget | au-delà |
|---|---|---|
| relance sous surveillance, après une modification | 2 secondes | le développeur commence à grouper ses modifications, et le test cesse de désigner laquelle a cassé |
série node surveillée complète, à froid |
10 secondes | le développeur change de fenêtre ; le retour de contexte coûte plus que l'épreuve, et le niveau glisse vers « avant un commit » |
La série surveillée s'adapte au nombre de cœurs, et le budget tient sur toute
machine. À partir de quatre cœurs, des processus complets portent la série
entière. En deçà, le lanceur n'en porterait la série que dans un seul
processus, et la relance qui suit un module de base — celui que presque toutes
les épreuves importent — dépasserait deux secondes. La série s'y joue donc en
threads sur tous les cœurs, et ses épreuves lourdes passent dans la série
node:long. Aucune épreuve ne disparaît : elle change de série, et ce qu'elle
éprouve ne dépend pas de la machine. Une garde refuse, dans ces séries, ce
qu'un thread refuserait et qu'un processus accepte, et le lanceur annonce à
chaque exécution le mode qu'il a retenu.
Pourquoi ces deux valeurs. En deçà de deux secondes, l'attention reste sur le
code et le résultat se lit comme la suite du geste. Au-delà de dix, elle part
ailleurs ; le niveau node cesse d'être lancé à chaque modification, et les
défauts qu'il attrapait se retrouvent par un chemin plus long, souvent au niveau
navigateur, qui ne dit plus quelle ligne les a produits. Ce sont des budgets,
c'est-à-dire des seuils de refus — non une prédiction de ce que la suite coûtera,
que seule la mesure du § 14.8 donnera.
Le budget est tenable par construction. Quelques centaines de tests sur des fonctions pures s'exécutent en une fraction de seconde. Le temps part ailleurs : dans le démarrage du lanceur, et dans trois ou quatre épreuves lourdes. Dépasser le budget n'est donc jamais le signe que la suite a grandi ; c'est le signe qu'un test a acquis un système de fichiers réel, un minuteur, ou une vraie recherche.
Les épreuves lourdes sont nommées et rangées à part, dans la série node:long
du § 14.4. L'audit par mutation n'y est pas (§ 14.13).
Deux règles tiennent le budget. Aucun test de la série surveillée n'attend un délai — un minuteur est la façon ordinaire dont une série de deux secondes en devient une de quarante. Aucun n'écrit hors d'un répertoire temporaire, dont le stockage reçoit le chemin en paramètre : un module qui ne sait pas où il écrit ne se teste pas en parallèle.
La mesure est affichée, le refus est généreux. Le lanceur imprime à chaque
exécution la durée totale et les dix tests les plus lents ; la construction échoue
quand la série surveillée dépasse trente secondes. Un refus calé sur le budget
lui-même serait capricieux sur une machine chargée, et un test capricieux se
désactive — ce qui coûte plus cher que le dépassement qu'il annonçait. La série
node:long porte son propre plafond, posé après sa première mesure. La dérive se
lit dans la liste bien avant d'être fatale.
Deux contrôles qui balayent une arborescence sont bornés : la cohérence des
versions (§ 18.5) et l'appariement captures/manifeste (§ 19.7). Chacun déclare son
périmètre — l'arbre des sources pour l'un, les sources de documentation et le
manifeste pour l'autre —, exclut les dépendances et les sorties de construction,
refuse de passer sur un balayage vide, et est montré en train d'échouer :
modifier version.json seul fait tomber le premier ; remplacer une image par une
version antérieure fait tomber le second.
14.15 Ce que l'exécutable emporte, et ce qu'il n'emporte pas
La suite d'épreuves n'est pas livrée : lanceur, données d'épreuve et moteur de rendu de test sont des dépendances de développement, et les données d'épreuve contiennent des cas qui n'ont rien à faire chez l'opérateur. Cette propriété est gardée, non supposée, par le test de construction du § 14.9.
Mais le travail d'épreuve laisse trois pièces dans l'application livrée, et ce sont celles que l'exigence « des tests unitaires dans l'application » vise réellement :
- le vérificateur d'invariants du § 14.12, appelé par les commandes et au chargement d'un fichier — une incohérence se refuse au moment du geste plutôt que de se découvrir sur une feuille imprimée ;
- le vérificateur d'indicateurs, qui tient la garde du § 12.3 : la page refuse de dessiner une courbe au-dessus de son plafond et le dit ;
- le contrôle de cohérence du § 8.8, qui est le même réflexe appliqué au fichier : le logiciel refuse d'ouvrir ce qu'il ne peut pas justifier, et le nomme.
Ces trois pièces sont du code de production, tenu par les mêmes épreuves que le reste. Ce qui reste dehors est le harnais, jamais la vérification.
15. Les configurations de démonstration
Elles servent à trois choses : montrer le logiciel sans saisir 260 personnes, enseigner la lecture des indicateurs, et servir de témoin de test. Une démonstration qui change d'un lancement à l'autre ne remplit aucun des trois.
Charger une démonstration crée une configuration neuve, nommée d'après elle suivie d'un numéro d'ordre, ouverte en lecture. Le logiciel n'écrase jamais la configuration courante et ne modifie jamais la démonstration d'origine : l'opérateur qui explore pendant une présentation ne doit pas pouvoir détruire son vrai plan d'un clic.
15.1 La grande démonstration — 260 membres
| grandeur | valeur |
|---|---|
| membres | 260 |
| sièges par défaut | 8 |
| tables | 33 : 29 à 8 sièges, 4 à 7 |
| sièges | 29×8 + 4×7 = 260, aucun libre |
| animateurs | 33, un par table, pris parmi les 260 |
| mobiles | 227 |
| tours | 4 |
| appartenances | tirées, de 1 à 12 membres |
Le calcul, et pourquoi il ne tombe pas juste tout seul. 260 ÷ 8 = 32,5 : aucun nombre entier de tables de 8 ne reçoit 260 personnes. 33 tables de 8 offrent 264 sièges, soit 4 vides. Le logiciel règle cela par la capacité individuelle, et la démonstration exerce ainsi, sur son premier écran, la fonction « défaut global, exception par table ».
La configuration est exactement tendue : la capacité libre vaut 227, soit exactement le nombre de mobiles. Chaque tour est une partition exacte, le moteur n'a jamais la liberté de déplacer quelqu'un vers une table moins remplie, et tout mouvement doit être un échange. C'est une propriété de l'espace de recherche, pas un détail.
Ce que la configuration rend possible, avant la génération :
| plafond a priori | |
|---|---|
| un mobile passant par quatre tables de 8 | 28 |
| un mobile passant une fois par une table de 7 | 27 |
| un animateur d'une table de 8 | 28 |
| les quatre animateurs des tables de 7 | 24 |
Ces quatre-là sont, par construction, les personnes les plus mal servies de la démonstration. Comme le minimum se calcule sur tous les participants placés, c'est ce 24 qui gouverne le minimum global, et aucune relance ne l'améliorera jamais. C'est précisément ce que la ligne secondaire « ancrés » du § 5.4 permet de lire sans conclure à un défaut du moteur — et c'est la démonstration la plus utile du catalogue, puisqu'elle montre en une lecture pourquoi les deux populations sont séparées.
99 retours imposés (33 × 3), attribués aux réservations et non additionnés aux autres.
Les animateurs sont tirés sans égard à leur appartenance, et aucun regroupement ne leur en impose une commune — sinon chaque mobile accumulerait au moins 3 redondances qu'aucune recherche ne réduit, et la démonstration vedette illustrerait une impossibilité. Un test le garde.
La variante sans exception — 33 tables toutes à 8, 4 places libres — est livrée à côté. Quatre sièges de marge rendent au moteur la liberté que la configuration principale lui refuse ; comparer les deux mesure ce que coûte l'absence de marge. Dans cette variante, les tables incomplètes changent d'un tour à l'autre, donc le plafond réalisé varie d'une proposition à l'autre : c'est elle, et non la principale, qui est la configuration témoin de la vue de comparaison du § 12.10.6, puisqu'elle est la seule où les escaliers de plafond diffèrent entre deux propositions.
La grande démonstration ne décerne jamais « minimum atteint ». Les tables de 7 offrent 112 sièges-tours dont 16 aux animateurs, donc 96 sièges-tours mobiles qu'un placement ne peut pas ne pas servir ; chaque mobile n'en absorbant que 4 au plus, au moins 24 mobiles terminent sous leur plafond a priori, et l'écart d'itinéraire maximal ne descend jamais sous 1 (§ 12.10.4). Le certificat se lisant contre le plafond a priori (§ 5.5), il est hors d'atteinte ici — et c'est une propriété de la configuration, non un défaut du moteur.
Un témoin de pagination à 257 badges accompagne le catalogue sans en être une cinquième entrée : c'est une donnée d'épreuve (§ 14.9), qui donne 65 planches A4 et 129 planches Lettre (§ 11.2). Les configurations dont une épreuve ou une capture d'écran a besoin et que le catalogue ne porte pas se construisent par des scénarios courts (§ 19.3) ; le catalogue reste à quatre entrées, et l'étape qui lit leur nombre le lit dans le catalogue plutôt que de l'écrire.
15.2 La distribution des appartenances
Loi discrète sur 1..12, à poids fixés, décrochant après 3 :
| taille | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| poids | 24 | 20 | 16 | 8 | 6 | 5 | 4 | 3 | 2 | 2 | 1 | 1 |
Somme 92 ; les tailles 1 à 3 pèsent 65,2 % des groupes ; taille moyenne théorique 3,446, d'où environ 75 groupes pour 260 membres.
Le compte réellement obtenu n'est pas une donnée de cette spécification : il dépend de la graine, fixée dans le dépôt. Le fichier livré porte le profil qu'il a effectivement produit, et c'est ce profil, lu dans le fichier, qui est la valeur attendue du test. Inscrire ici des effectifs tirés donnerait une certitude fausse.
La règle du dernier groupe : tirer tant que le total reste sous 260, puis tronquer le dernier au reste.
La faisabilité se construit, elle ne se déduit pas d'une inégalité. « Le plus gros groupe tient dans le nombre de tables » est nécessaire mais ne suffit pas : trois tables de 10, 1 et 1 ne peuvent séparer quatre groupes de 3, parce que la table de 10 exigerait 10 appartenances distinctes. Le logiciel vérifie deux inégalités — plus gros groupe ≤ nombre de tables, plus grande capacité − places vides ≤ nombre de groupes — puis construit un tour témoin. Les places vides sont les sièges qui restent une fois chaque membre assis, zéro quand la salle n'en laisse pas : une table reçoit au moins sa capacité moins ces places, chacun de ses occupants d'une appartenance différente. Dans une salle exactement pleine, la seconde inégalité se lit « plus grande capacité ≤ nombre de groupes » ; ailleurs, cette lecture refuserait à tort deux tables de 3 pour deux membres d'appartenances différentes, qu'un tour sépare. Le générateur refuse une graine dont aucun tour témoin ne se construit.
15.3 La petite démonstration — 12 membres
| grandeur | valeur |
|---|---|
| membres | 12 |
| tables | 4, de 3 sièges |
| tours | 4 |
| réservations | aucune |
| appartenances | 4, 4, 4, fixées à la main |
Elle tient sur un écran, et c'est sa raison d'être : la grande noie un défaut dans 33 tables, la petite le montre d'un coup d'œil.
Elle admet un plan parfait, et ce plan est écrit. Avec 4 tours et 2 voisins par tour, nul ne peut rencontrer plus de 8 personnes sur 11.
| tour | table 1 | table 2 | table 3 | table 4 |
|---|---|---|---|---|
| 1 | 1, 2, 3 | 4, 5, 6 | 7, 8, 9 | 10, 11, 12 |
| 2 | 6, 9, 12 | 3, 8, 11 | 2, 5, 10 | 1, 4, 7 |
| 3 | 4, 8, 10 | 2, 7, 12 | 1, 6, 11 | 3, 5, 9 |
| 4 | 5, 7, 11 | 1, 9, 10 | 3, 4, 12 | 2, 6, 8 |
Appartenances : A = {1, 5, 8, 12}, B = {2, 4, 9, 11}, C = {3, 6, 7, 10}. Chaque table réunit à chaque tour un membre de chacune. Vérifié par énumération : partition valide à chaque tour, zéro paire répétée, chacun visite les quatre tables, 8 rencontres pour les douze, zéro collision. Le logiciel doit afficher « minimum atteint », les compteurs de collision et de répétition mesurés à zéro, et la ligne secondaire « ancrés » à « — » : cette démonstration n'ayant aucune réservation, sa population d'ancrés est vide, et le § 5.4 exige le tiret, jamais zéro.
La variante « conflit inévitable » ne change qu'une colonne : appartenances de tailles 5, 4, 3. Cinq personnes ne se répartissent pas sur quatre tables sans que deux se retrouvent : au moins une collision par tour, quatre collisions cumulées sur les quatre tours, quel que soit l'algorithme — et au moins une paire distincte, jamais quatre : un plan qui garde les deux mêmes personnes ensemble aux quatre tours n'en réunit qu'une. Les deux planchers sont dans deux unités différentes et le logiciel ne les confond jamais (§ 5.4).
Cette variante est aussi l'instance la plus courte où le classement du § 5.7 se lit : le groupe de 5 porte dix paires possibles pour quatre collisions forcées, donc l'excédent n'est pas forcé — un plan peut répartir ses quatre collisions sur quatre paires distinctes, et c'est celui que le logiciel met en tête, devant un plan qui les concentrerait sur une seule.
Une personne déplacée d'un groupe à l'autre fait basculer le logiciel de « minimum atteint » à « quatre collisions cumulées inévitables » : c'est le contraste le plus court que la spécification puisse offrir.
15.4 Les cas limites, exercés plutôt que supposés
- Une seule appartenance — la contrainte de séparation est vide de sens : le logiciel doit l'annoncer et proposer de la désactiver, non afficher 12 conflits sans commentaire.
- Une seule table, un seul tour — les indicateurs de répétition doivent valoir zéro, non lever une erreur de division.
- Des noms longs — la grande démonstration contient une poignée de noms proches de la limite, dont un de glyphes larges et un à diacritiques. Sans eux, une démonstration faite de noms courts laisse les seuils faux jusqu'à la première vraie liste.
Le générateur refuse une configuration à zéro participant ou à zéro table : un fichier vide passe tous les tests sans rien démontrer.
15.5 La reproductibilité
- Un seul générateur pseudo-aléatoire, à état entier et transition explicite, accompagné d'un vecteur de test qu'un test rejoue.
- Une graine entière écrite dans la définition, pas enfouie dans le code.
- L'ordre des tirages fait partie du contrat : insérer un tirage au milieu décale tout ce qui suit.
- Rien de dépendant de l'environnement : aucun
localeCompare, aucun parcours reposant sur l'ordre d'itération d'un objet ; deux éléments de même rang se départagent par leur identifiant entier. Cette règle vaut pour le moteur autant que pour le générateur : sans elle, le même code et la même graine donnent deux placements sur deux moteurs d'exécution, et toute la reproductibilité des captures d'écran tombe (§ 19.4). - Le fichier livré est la source de vérité. Un test rejoue le script
générateur et compare l'empreinte au fichier livré. L'empreinte porte sur la
charge utile, l'en-tête exclu (§ 8.8) : sans cette restriction,
produit_versionla fait échouer à chaque livraison, et le geste conscient qu'elle réclame devient mécanique, donc aveugle. La régénération devient un geste conscient, et non une dérive qui change les démonstrations sous les pieds de l'opérateur. L'échec nomme ce qui a changé. - Le placement, lui, n'est pas figé : la démonstration fixe les entrées, le moteur reste libre. Figer le résultat empêcherait de montrer la comparaison de plusieurs propositions, qui est le cœur du logiciel. Cela ne veut pas dire que le moteur dispose d'une source de hasard libre : il prend une graine et un compte d'arrêt (§ 5.7), et à graine et entrée égales il rend le même plan. Ce qui n'est pas figé est le stockage d'un résultat dans la démonstration, pas le déterminisme du calcul.
15.6 Les noms : inventés, lisibles, sans collision
Une liste de chaînes aléatoires est illisible en démonstration, et des noms réels n'ont pas leur place dans un dépôt. Le logiciel compose des noms forgés mais prononçables, par produit cartésien tiré sans remise — ce qui garantit l'unicité sans boucle de rattrapage, donc sans tirage supplémentaire qui décalerait la suite.
Les personnes : un prénom courant et un patronyme forgé par assemblage d'un préfixe et d'une terminaison. Le réservoir compte au moins 320 combinaisons, plus que les 260 personnes : un réservoir plus étroit forcerait des homonymes de famille, que l'œil lit comme des parentés sur une feuille de table.
Les appartenances : une tête de nom d'organisation — Ateliers,
Coopérative, Verrerie — et une racine forgée. La tête dit immédiatement
qu'il s'agit d'une organisation. Le réservoir se dimensionne sur le maximum
structurel : une loi qui peut tirer 260 groupes d'une personne exige 260
combinaisons. Le générateur refuse de produire une démonstration dont le
compte de groupes dépasse son réservoir.
Un test vérifie l'unicité et qu'aucun fragment forgé n'apparaît dans le dépôt hors des listes du générateur, des fichiers de démonstration et des CSV d'exemple. Le balayage s'étend aux données d'épreuve (§ 14.9), dont les noms inventés sont disjoints de ce réservoir : une donnée d'épreuve qui emprunte une racine d'organisation plausible fait échouer la garde, et c'est ce qui l'empêche de tomber au premier exemple écrit de bonne foi. Il refuse de passer sur un balayage vide. Ce test n'établit rien sur le monde réel : aucune vérification locale ne peut prouver qu'un nom forgé ne désigne nulle part une organisation existante.
16. Livraison
L'exécutable Windows se construit depuis Linux par electron-builder, qui
emballe la coquille Electron du projet et le même www/ que servent les
plateformes de Capacitor (§ 13.1). La construction s'exécute dans un conteneur,
sous une image épinglée par empreinte, de sorte que la machine de développement
n'a rien à installer en dehors du projet. La cible portable n'exige pas Wine :
electron-builder édite lui-même les ressources de l'exécutable. L'image n'en
porte donc pas, et une cible qui l'exigerait — un installateur — y échoue au lieu
de produire le livrable que ce paragraphe exclut. Le conteneur ne reçoit aucune
variable de l'hôte : un numéro de construction d'intégration continue y
remplacerait le quatrième champ de la version Windows (§ 18.1).
Cette capacité est à vérifier dès la première semaine, sur un squelette vide, au même titre que la publication du dossier d'origine par le lanceur portable (§ 8.6). Découvrir en fin de projet qu'une étape exige une machine Windows coûte beaucoup plus cher que de le découvrir tout de suite.
Ce qui est livré au client : un exécutable, et rien d'autre à installer.
Le livrable est un exécutable portable à fichier unique, non un installateur, et ce n'est pas une préférence : la règle du dossier de travail du § 8.6 en dépend. Un installateur qui rangerait l'exécutable sous un emplacement de données applicatives y ferait réussir la sonde d'écriture, et le logiciel créerait son dossier de données exactement là où le § 8.6 l'interdit.
La construction refuse de produire un exécutable dont la version n'a pas de section au changelog (§ 18.4), et refuse d'écraser un exécutable portant le même nom dans le dossier de sortie (§ 18.2).
16.1 Les documents livrés
| document | contenu |
|---|---|
GUIDE-WINDOWS.md |
obtenir l'exécutable, le lancer, la règle du dossier de travail et ses deux issues (§ 8.6), la commande qui ouvre ce dossier, comment sauvegarder, que faire si l'antivirus bloque, où lire la version et comment signaler un problème |
GUIDE-USAGE.md |
le parcours d'une soirée, de la liste de participants à l'impression, écran par écran |
CHANGELOG.md |
ce qui change d'une version à l'autre, et ce qu'il faut faire en remplaçant (§ 18.4) |
Le GUIDE-USAGE.md suit les douze étapes du § 2.1, et le scénario de pilotage
les implémente : chaque capture d'écran déclare l'étape à laquelle elle
appartient, et un contrôle du niveau node échoue si une étape n'a aucune
capture, ou si l'ordre des captures contredit l'ordre des étapes (§ 19.3). Deux
sources pour une même séquence sont le mode de défaillance que toute cette
spécification combat — à ceci près qu'il est ici mécaniquement détectable, donc
qu'il se garde au lieu de se surveiller. La liste des écrans est celle du
§ 19.3.
GUIDE-WINDOWS.md sort de la boucle d'engendrement (§ 19.8) : le pilotage
s'exécute sur web sous Linux, et les écrans que ce guide décrit appartiennent
à electron sous Windows. Il ne cite jamais un nom d'exécutable complet : il
cite le gabarit et désigne « le fichier dont le nom commence par
gestion_table_tournante_libre_v » — un nom daté change à chaque livraison, et
une phrase qui en cite un envoie l'opérateur chercher un fichier qui n'existe pas.
Il n'écrit pas davantage de phrase fixe sur l'emplacement des fichiers : il énonce
la règle du § 8.6, ses deux issues, et la commande qui ouvre le dossier.
Le guide d'usage se rédige à partir des configurations de démonstration, pour que le lecteur reproduise exactement ce qu'il lit. Il est relu par quelqu'un qui n'a pas écrit le logiciel ; un guide relu par son auteur ne révèle aucune étape manquante.
16.2 Le poste de développement
Le projet s'installe et se lance d'une commande, la même sur chaque système Linux, NixOS compris, et sous macOS ; Windows a la sienne. Une étape laissée à la main est celle qu'un nouveau venu oublie, sans savoir ensuite laquelle.
| commande | ce qu'elle fait |
|---|---|
./install.sh |
pose les paquets du système qui manquent — par apt, dnf, zypper ou pacman, sous sudo —, puis le Node du projet et les dépendances que fixe package-lock.json ; relancée, ne refait que ce qui manque |
./install_dev.sh |
ajoute le Chromium des épreuves du navigateur, des polices et, sur amd64, podman, sous lequel se construit l'exécutable |
./run.sh |
lance la coquille Electron sur une session graphique ; sans écran, ou là où Electron n'est pas publié, sert l'application à un navigateur par le serveur de Vite |
make |
lance l'application comme ./run.sh ; make help nomme les autres cibles — les trois séries d'épreuves, la couverture (§ 14.13), la construction, le contrôle de version (§ 18.5), l'essai de démarrage |
Le Node du projet a une seule source : la majeure écrite dans
.node-version, que lisent les scripts bash, les scripts PowerShell et
shell.nix. L'installation télécharge cette version de nodejs.org, la vérifie
contre sa somme et la range hors du dépôt, dans le dossier de l'utilisateur :
elle n'exige aucun droit d'administration et ne dépend pas du Node que porte —
ou ne porte pas — le système. Chaque cible du Makefile passe par ce Node.
Sous Windows, install.cmd, install_dev.cmd et run.cmd passent la main à
des scripts PowerShell 5.1, la version que porte tout Windows. Sous NixOS,
où les binaires téléchargés ne trouvent ni leur chargeur ni leurs bibliothèques
aux chemins qu'ils attendent, scripts/installation/shell.nix déclare Node,
Electron et Chromium d'un nixpkgs épinglé par révision et par somme ; les scripts
du projet s'y relancent d'eux-mêmes.
make verifier_systemes éprouve l'installation elle-même. Sur chaque système
du catalogue — les familles Debian, Fedora, openSUSE et Arch, et Nix —, un
conteneur podman exécute, sous un compte ordinaire qui passe par sudo,
./install_dev.sh, puis les trois séries d'épreuves, l'essai de démarrage
d'Electron et le serveur de Vite ; les scripts PowerShell s'y éprouvent sous
pwsh. Ce qu'un conteneur n'éprouve pas est nommé, et reste à vérifier sur une
machine : le bac à sable d'Electron, une autre architecture que celle de l'hôte,
NixOS lui-même, une vraie session graphique, Windows et macOS réels.
17. Ce que ce document ne tranche pas
Les numéros ne bougent pas : ils sont cités ailleurs. Quatre points sont désormais tranchés et le disent ; deux reçoivent leur instrument sans que leur valeur soit fixée ; un seul reste entièrement ouvert.
- TRANCHÉ — le format de stockage des placements. La saisie reste en champs nommés, les placements engendrés passent à la forme positionnelle : § 8.9.
- Tranché pour moitié — la bibliothèque PDF. La question de l'ingestion du
SVG disparaît : la bibliothèque ne dessine plus que du texte, des filets et
des rectangles (§ 11.1). Reste ouvert laquelle, sur un cahier des charges
désormais étroit : exécution sous
node, police embarquée en latin étendu, tableToUnicodeécrite, boîte de page exacte, couche de texte extractible, et la possibilité de neutraliser la date de création et l'identifiant de document. S'y ajoute le choix de l'outil d'extraction qui éprouve le résultat, dont le test dépend entièrement. - TRANCHÉ — la désignation des animateurs est manuelle. Il n'existe aucune commande qui les répartit : § 5.8 et § 2.1, étape 6.
- Le coût de la maximisation du plafond a priori sur les itinéraires
admissibles. Son instrument est nommé — le banc du § 19.10 le relève sous
nodesur la grande démonstration et sa variante — et le point porte désormais trois usages au lieu d'un : le diagnostic, le troisième chiffre de la page de qualité, et le départage du § 5.7. Tant qu'il n'est pas mesuré, les deux derniers s'effacent proprement : « inconnu », et critère sauté nommé. - TRANCHÉ — la comparaison trace le manque. § 12.10, et sa configuration témoin est la variante sans exception (§ 15.1).
- Le seuil de zoom qui retire les listes de noms. Son instrument est nommé : le banc du § 19.10 relève le temps par image à plusieurs zooms sur les 33 tables avec listes. Aucune capture d'écran ne le mesure — une image est muette sur la durée qui l'a produite. Ce qui reste à dire est ce qu'est un temps par image acceptable sur la machine de l'opérateur, qui n'est pas celle du développement.
- OUVERT, et hors périmètre de la première itération — les dégagements de référence d'une salle. Le mot « conforme » reste absent de l'interface et la mention « disposition relative » reste sur chaque impression du plan (§ 7.7). L'unité étant déjà le centimètre réel, l'ajouter plus tard ne coûte aucune migration.
18. La version, le nom du livrable, le changelog
Le logiciel se livre comme un fichier unique que l'opérateur copie lui-même. Il n'existe ni dépôt de paquets, ni mise à jour automatique, ni canal par lequel le logiciel pourrait annoncer son âge. Ce qui permet de dire laquelle de ces copies est laquelle tient donc entièrement dans le nom du fichier, dans ce que l'application affiche et dans le changelog — et cette section existe pour que ces endroits ne puissent pas se contredire.
18.1 Le schéma : AAAA.MM.JJ.NN
Une version est la date de la livraison suivie d'un rang dans la journée, sur quatre champs, tous présents, tous à longueur fixe :
2026.10.05.01 première livraison du 5 octobre 2026
2026.10.05.02 seconde livraison du même jour
2026.01.05.01 5 janvier 2026 — le mois garde son zéro de tête
Les quatre champs sont toujours écrits : l'année sur 4 chiffres, le mois et le jour sur 2, le rang sur 2, zéros de tête compris. Un format qui change de forme entre deux livraisons — le rang n'apparaissant qu'à partir de la seconde — casse le tri d'un dossier, la comparaison de deux noms et l'expression régulière du contrôle du § 18.5.
Pourquoi la date. L'opérateur et celui qui reçoit son signalement n'ont qu'une
chose en commun : le moment où le fichier a été envoyé. Un numéro de publication
oblige à ouvrir le changelog pour savoir s'il est vieux de trois jours ou de trois
mois. AAAA.MM ne suffit pas : pendant la préparation d'un événement, plusieurs
livraisons dans le même mois sont la règle.
Pourquoi pas un versionnage sémantique. Personne ne consomme ce logiciel comme une dépendance : il n'y a ni interface de programmation publiée, ni résolution de contraintes, ni arbitre pour trancher « est-ce un changement cassant ». Un champ majeur mal choisi est une erreur muette, qu'aucun contrôle ne peut relever. Ce que le versionnage sémantique servirait à dire ici — « ce que tu enregistres aujourd'hui, la version d'hier ne le modifiera plus » — est dit par la version de format des fichiers du § 8.8, qui est un nombre distinct et le reste (§ 18.4).
L'ordre. Deux versions se comparent champ par champ comme quatre entiers. Les champs étant à longueur fixe, l'ordre alphabétique des noms de fichiers coïncide avec l'ordre chronologique : un opérateur qui trie son dossier par nom lit sa version courante sur la dernière ligne, sans rien savoir du schéma.
Le rang. Il repart à 01 chaque jour et ne décroît jamais. Un numéro est
consommé au moment où l'exécutable quitte la machine qui l'a construit, et
cesse alors d'être réutilisable : deux binaires différents répondant à un même
nom, chacun dans une main différente, rendent irréparable le seul moment qui
compte — l'opérateur qui décrit un défaut en nommant sa version. Un exécutable
construit puis jeté sans être envoyé n'a consommé aucun numéro.
Le rang plafonne à 99. La construction refuse le centième et le dit. Élargir le champ détruirait la propriété de tri que toute la section achète, et cent livraisons dans une journée désignent un autre problème que le format du numéro.
La forme technique. Le fichier de description du projet, package.json,
exige trois champs entiers sans zéro de tête — le format de version qu'il
valide refuse 0105 — et electron-builder en tire la ressource de version de
l'exécutable Windows. Le logiciel dérive donc le mois et le jour en un entier,
MM × 100 + JJ, écrit sans remplissage, et le rang en entier :
version affichée 2026.10.05.01 2026.01.05.01
version technique 2026.1005.1 2026.105.1 (AAAA . MM×100+JJ . NN)
version Windows 2026.1005.1.0 2026.105.1.0
La dérivation est monotone — MM × 100 + JJ croît strictement avec la date,
donc 2026.105.1 < 2026.1005.1 < 2026.1231.1 < 2027.105.1 — et bijective : le
jour étant inférieur à 100, un entier se redécompose d'une seule façon. Un
contrôle refait le trajet dans les deux sens et refuse une divergence. Les trois
nombres tiennent dans les 16 bits que le format de ressources Windows impose à
chacun de ses quatre champs : l'année, MM × 100 + JJ qui vaut au plus 1231, et
le rang.
Quand la plateforme android sera ajoutée, son versionCode, un entier
unique, dérivera de la même source sous la forme AAMMJJNN sur huit chiffres —
26100501 —, monotone et sous la borne de 2 100 000 000 qu'impose la
plateforme, et rejoindra le contrôle du § 18.5.
La longueur fixe ne vaut que pour la forme affichée et pour le nom de fichier, qui sont les seules qu'un humain trie. La forme technique n'est jamais lue par l'opérateur et n'est jamais triée.
18.2 Le nom du livrable
gestion_table_tournante_libre_v<AAAA>_<MM>_<JJ>_<NN>.exe
gestion_table_tournante_libre_v2026_10_05_01.exe
Les séparateurs de la version deviennent des soulignés. Le nom ne contient
alors qu'un seul point, celui de l'extension : tout outil qui coupe un nom à
son dernier point, et le réglage de Windows qui masque les extensions connues,
laissent la version entière visible. Un nom portant v2026.10.05.01 la ferait
amputer par les outils qui coupent au premier point.
Le nom n'est jamais saisi. Il est calculé à la construction depuis l'unique source du § 18.3, par substitution des points par des soulignés. Un nom tapé à la main finit par mentir sur son contenu, et le signalement arrive sur cette copie-là.
La construction refuse d'écraser un exécutable portant le même nom dans le dossier de sortie. Ce refus est un garde-fou local — le § 18.5 énonce ce qu'il ne couvre pas — et une option explicite le lève pour reconstruire un numéro qui n'a pas été livré.
Le guide ne cite jamais un nom complet, il cite le gabarit (§ 16.1).
18.3 Une seule source, quatre destinations
La version est écrite à un seul endroit, un fichier version.json à la racine
du projet :
{ "version": "2026.10.05.01" }
Tout le reste en dérive, par un script de construction :
| destination | forme | usage |
|---|---|---|
package.json |
technique | ce qu'exigent npm et electron-builder, qui en tire la ressource de version Windows |
nom de l'exécutable, posé dans la configuration d'electron-builder |
soulignés | ce que l'opérateur voit dans son dossier |
src/version.genere.js |
affichée et technique | ce que l'application affiche |
titre de la section de tête du CHANGELOG.md |
affichée | ce que l'opérateur lit avant de remplacer |
Le module engendré est versionné dans le dépôt, et un contrôle rejoue le script puis compare — la même discipline qu'au § 15.5 pour les fichiers de démonstration (§ 13.1).
Le module porte aussi la provenance de la construction — livraison, séance de développement, ou construction de documentation — posée par la commande qui l'engendre, jamais lue dans l'horloge ni dans l'environnement. Sans elle, une capture d'écran prise pendant le développement annonce un numéro déjà livré, et le diagnostic porte sur un binaire que personne ne détient.
18.4 Le CHANGELOG.md
Son lecteur est l'opérateur qui décide s'il remplace son exécutable. Il est rédigé en français, la version la plus récente en tête, et il n'est jamais tronqué.
Une section par version :
## 2026.10.05.01 — 5 octobre 2026
**Ce qui change pour vous**
- Le plan de salle se recadre sur la table sélectionnée.
- L'import d'un tableur accepte la tabulation comme séparateur.
**Ce qui est corrigé**
- Un nom portant des accents, importé depuis un tableur enregistré par un
logiciel qui n'annonce pas son encodage, s'affichait avec des caractères
parasites sur le plan imprimé.
- Une table ramenée à sa capacité par la poignée pouvait désasseoir une personne
à un tour qui n'était pas affiché.
**Version de format des fichiers** : inchangée.
**En remplaçant** : rien à faire. Copier le nouvel exécutable par-dessus
l'ancien ; les événements déjà enregistrés s'ouvrent sans manipulation.
La date en toutes lettres du titre n'est pas une seconde source : le contrôle la réengendre depuis la version et compare.
Les deux premières rubriques s'omettent quand elles sont vides. Les deux dernières lignes sont obligatoires : ce sont celles qui répondent à la question que l'opérateur se pose. Une rubrique absente se lit comme un oubli ; un « rien à faire » écrit est une réponse.
La ligne de format est obligatoire parce qu'elle seule annonce un échange devenu à sens unique. Quand la version de format monte, un événement ouvert en écriture par la nouvelle version se convertit (§ 8.8) ; l'ancienne version ne le rouvrira plus qu'en lecture seule. La ligne dit donc, dans ce cas, de conserver une copie du fichier avant la première modification — la conversion n'a pas lieu à l'ouverture, et c'est exactement le moment où l'opérateur peut encore se ménager un retour en arrière.
L'épreuve de ce qui entre. Une ligne du changelog a pour sujet ce que l'opérateur voit, fait ou subit. Une ligne dont le sujet est un module, un fichier source, une bibliothèque ou un test n'y entre pas : ce récit vit dans l'historique du dépôt. Une correction se décrit par son symptôme visible — « les accents s'affichaient de travers sur le plan imprimé » — et non par sa cause dans le code : l'opérateur reconnaît ce qu'il a vécu, pas ce qui l'a produit.
Il n'existe aucune section « à venir ». Une version existe lorsqu'un exécutable a été livré. Une section d'intentions transforme le fichier en feuille de route, et l'opérateur cesse de lire la première section comme « ce que je peux avoir ».
La section de tête s'écrit donc au moment de la livraison. La commande de
livraison refuse de construire si la section de tête manque, si son titre ne
reproduit pas la version de version.json, si son corps est vide, ou si l'une des
deux lignes obligatoires manque. Une livraison dont le motif n'est écrit nulle part
est une livraison dont l'opérateur ne peut pas décider.
18.5 Le contrôle qui interdit la divergence
Chaque endroit où un chiffre se recopie est une occasion de diverger. Un contrôle
de la série node surveillée (§ 14.4) échoue quand :
- la version technique de
package.jsonn'est pas celle que la dérivation du § 18.1 produit depuisversion.json, ou ne se redécompose pas en la version affichée ; - le module engendré ne reproduit pas, caractère pour caractère, ce que le script réengendre ;
- le titre de la première section du
CHANGELOG.mdn'est pas la version courante, sa date en toutes lettres n'est pas celle que la version engendre, ou son corps n'a pas ses deux lignes obligatoires ; - les sections du changelog ne sont pas en ordre strictement décroissant, ou deux sections portent la même version ;
- la date inscrite dans la version dépasse de plus d'un jour la date civile locale de la machine qui construit. La tolérance absorbe l'écart de fuseau ; ce que le contrôle attrape est une année ou un mois tapé de travers, pas une minute ;
- une chaîne de la forme affichée apparaît ailleurs que dans
version.json, le module engendré et le changelog, ou une chaîne de la forme technique ailleurs que danspackage.jsonet le module engendré ; - un document construit porte le littéral
#_#, ou cite un nom d'exécutable qui ne se conforme pas au gabarit du § 18.2. Un gabarit non substitué se lit comme un nom de fichier et envoie l'opérateur chercher un fichier qui n'existe pas. Le contrôle ne peut pas exiger qu'un nom cité figure parmi les sorties de la construction : un gabarit ne satisfait par construction jamais cette condition, et la seule formulation qui tienne échouerait ; - son balayage ne trouve aucun fichier à examiner (§ 14.2).
Le périmètre du balayage du point 6 se pose à l'écriture du contrôle, pas
après. Il porte sur l'arbre des sources, les fichiers de configuration et les
scripts de construction, à l'exclusion des données engendrées — fichiers de
démonstration qui portent produit_version, manifeste des captures d'écran,
données d'épreuve —, des guides, des épreuves de la dérivation elle-même qui citent
des exemples par nécessité, des dépendances et des sorties de construction. Sans
cette exclusion le contrôle échoue au premier jour, et un contrôle qui échoue en
permanence se désactive.
Un contrôle de fin de construction lit le nom du fichier réellement produit et
exige qu'il dérive de la même source. Ces contrôles se montrent en train d'échouer
(§ 14.2) : modifier version.json seul fait tomber les points 1, 2 et 3, et
l'échec nomme lequel des endroits est resté en arrière.
Ce que ces contrôles ne couvrent pas, et qui reste un geste humain.
Reconstruire après une modification du code sans changer version.json produit
un second binaire sous un numéro déjà livré, et aucun des huit points ne le voit :
le changelog n'a pas changé non plus, donc tout s'accorde. Le refus d'écrasement du
§ 18.2 n'attrape le cas que si l'exécutable précédent est encore dans le dossier de
sortie. La discipline qui tient réellement est celle du § 18.4 : livrer exige
d'écrire une section, et écrire une section pour une version déjà publiée fait
échouer le point 4.
Le déterminisme n'est pas entamé (§ 14.7). La version est une constante engendrée à la construction, jamais une lecture d'horloge à l'exécution. Le moteur et le générateur de démonstrations ne la lisent pas. Le script de construction, lui, lit la date civile au point 5 ; il n'appartient ni au moteur ni au générateur, sur lesquels seuls porte l'interdit.
18.6 Où la version se voit
Un opérateur qui signale un défaut sans pouvoir dire sa version coûte un aller-retour complet : la première réponse n'est pas un diagnostic, c'est une question. Le logiciel fait donc en sorte qu'on n'ait pas à la chercher.
| endroit | forme |
|---|---|
| bandeau d'application, sur tout écran (§ 8.4) | 2026.10.05.01, avec la provenance et — quand un plan est ouvert — le libellé de mode |
| premier écran (§ 8.1), liste des événements comme répertoire vide | version 2026.10.05.01 |
| titre de la fenêtre | Gestion table tournante Libre — 2026.10.05.01 |
| panneau « À propos » | un bloc d'une ligne que l'opérateur copie |
| pied de chaque page imprimée (§ 11.7) | la version, à côté de l'horodatage du placement |
| en-tête du fichier d'état (§ 8.8) | champ produit_version |
Le bandeau d'application est le porteur principal, et le titre de fenêtre son
complément. Un signalement arrive le plus souvent sous forme d'image, et une
capture faite à la touche d'impression d'écran, ou cadrée sur le défaut, ne
contient pas toujours la barre de titre du système — elle ne la contient jamais sur
la plateforme web, où le titre est celui d'un onglet. Un bandeau dessiné par
l'application est dans l'image quel que soit le cadrage. Le § 8.4 impose en plus un
cadre permanent autour du plan pour le mode : les deux coexistent, le cadre
signalant le mode là où la main agit, le bandeau portant la version jusque sur la
liste des événements.
Le bandeau annonce aussi la version de format du fichier ouvert dès qu'elle diffère de celle que le binaire écrit : plus ancienne, « ce fichier se convertira à votre première modification » ; plus récente, « ce fichier s'ouvre en lecture seule ». Le changelog annonce l'événement à la livraison ; le bandeau l'annonce au moment où l'opérateur peut encore se ménager un retour en arrière, c'est-à-dire avant le premier geste.
Le bloc copiable du panneau « À propos » tient en une ligne — version affichée,
version technique, plateforme, provenance de la construction, version de format
des fichiers — et se colle dans un courriel. Un chiffre recopié à la main se
recopie faux. Il porte les deux formes parce qu'un opérateur qui lit la fiche
de propriétés du fichier sous Windows y trouve 2026.1005.1.0 et non
2026.10.05.01 ; sans la correspondance écrite quelque part, il cite un numéro qui
ne figure dans aucun changelog.
Le pied des impressions porte la version parce qu'une feuille circule et survit à la séance. La règle vaut pour la page ; un carton découpé et porté au cou n'est pas une page et n'en porte rien (§ 11.4).
Le champ dans le fichier d'état enregistre la version qui a écrit le fichier en dernier. Il ne compromet pas la sérialisation canonique du § 8.8 — c'est une constante de construction, pas une valeur tirée de l'horloge — et il change une fois lors du premier geste suivant un remplacement d'exécutable, ce qui coûte un correctif et non du bruit à chaque geste. Il n'est pas la version de format, qui reste un nombre séparé, comparé selon ses propres règles.
Pendant une construction de documentation, le numéro est remplacé par une marque de provenance — dans le bandeau, au pied des pages imprimées et sur la feuille de contrôle des badges. La raison est au § 19.4 : sans cette marque, le texte relevé de chaque capture d'écran change à chaque incrément de version, et toutes les images se réécrivent. Le manifeste, lui, enregistre la version réelle, et le contrôle de version de la livraison la compare (§ 19.7).
19. Les captures d'écran, la documentation engendrée, le banc de mesure
Vocabulaire. Le § 7.3 emploie « capture » au sens de la capture du pointeur. Cette section écrit toujours « capture d'écran » en entier, et aucun autre texte n'emploie « capture » seul. Deux mécanismes sans rapport portant le même mot dans un même document se confondent à la lecture rapide — et la lecture rapide est celle que fait l'implémenteur qui cherche une règle.
19.1 Une seule boucle, pas deux chantiers
Le scénario de pilotage et la documentation sont un seul mécanisme. Le
scénario conduit l'application le long du parcours de l'opérateur (§ 2.1) et dépose
une capture d'écran à chaque étape ; la documentation affiche ces captures et rien
d'autre. GUIDE-USAGE.md (§ 16.1) est la lecture d'un parcours que la machine
vient de refaire.
La conséquence est la raison d'être du couplage : un guide ne peut plus vieillir en silence. Si une commande change de nom, si un écran disparaît, si un bouton cesse d'être atteignable, l'étape correspondante échoue, la construction de la documentation s'arrête en nommant l'étape, et elle ne republie pas les images du passage précédent : une documentation partielle qui se complète avec d'anciennes images est exactement le défaut que la boucle existe pour supprimer.
Le pilotage s'exécute sur la plateforme web (§ 13.1), servie en local. Le
pilote, le navigateur et son pilote de protocole sont des dépendances de
développement : l'exigence « aucune connexion requise » du § 2 porte sur le
logiciel livré, jamais sur l'atelier qui le construit.
Les scripts s'exécutent sous node, dans la même langue que tout le reste
(§ 13.2). Ils ne calculent aucun indicateur. Quand une étape vérifie un chiffre
affiché, elle appelle la fonction du moteur qui l'a produit et compare : la même
implémentation sollicitée deux fois, un oracle au sens du § 14.11, jamais une
seconde arithmétique.
19.2 Le pilote, et les propriétés dont le reste dépend
Le pilote est Selenium, conduit par sa liaison node. Le choix se remplace,
mais il engage quatre propriétés dont toute la section dépend, et une substitution
se juge sur elles, pas sur le nom :
- Des événements de pointeur que le navigateur traite comme réels. Sans eux, un glissement du § 7.1 ne se conduit pas et la moitié du parcours n'est pas atteignable.
- La capture d'écran d'un élément, et non de la fenêtre. Elle recadre sur la
surface de l'application : le cadre de fenêtre et la barre système — ce qui
diffère entre
websous Linux etelectronsous Windows — sortent de l'image, et le guide d'usage reste vrai sur la plateforme livrée. - La maîtrise de la taille du cadre d'affichage. Voir § 19.4 : la commande du standard dimensionne la fenêtre, pas le cadre d'affichage, et le logiciel ne s'en contente pas.
- Un navigateur lancé sans accès réseau hors de la boucle locale. Le § 2 interdit que quoi que ce soit sorte de la machine ; une requête sortante est alors un échec de connexion observable, et non une requête qu'il faudrait avoir su écouter.
Deux propriétés que le logiciel ne demande pas au pilote, parce qu'aucun pilote ne les offre de façon portable :
- La lecture de la console. Le logiciel installe donc, avant le chargement de
ses propres scripts, un collecteur de page —
error,unhandledrejection, et l'enveloppe deconsole.error— qui accumule dans un tableau que chaque étape relit. Une étape qui trouve ce tableau non vide échoue en citant la première entrée. Le mécanisme est portable et il attrape ce qu'un journal de console n'attrape pas : une promesse rejetée sans gestionnaire. - L'émulation d'une préférence système. Le logiciel règle son propre réglage de thème, qui est celui que l'opérateur manipule (§ 13.3), et injecte une feuille de style qui annule transitions et animations. La préférence système n'est consultée qu'au premier lancement, que le profil neuf du § 19.4 produit à chaque exécution : le scénario la pose explicitement et ne la déduit jamais.
19.3 Le parcours, et ce qu'il photographie
Un parcours ordonné, du premier écran à l'impression. Son ordre est celui des
douze étapes du § 2.1, et c'est la seule source de cet ordre : chaque capture
d'écran déclare l'étape à laquelle elle appartient, et un contrôle du niveau node
échoue si une étape n'a aucune capture, ou si l'ordre des captures contredit
l'ordre des étapes.
Chaque étape affirme une condition visible avant de prendre sa capture d'écran — l'élément nommé est présent et porte le libellé attendu — et l'exécution s'arrête sur la première condition fausse et sur la première entrée du collecteur d'erreurs. Une étape n'attend jamais un délai pour laisser le logiciel rattraper son retard : elle attend un état. Un délai n'est légitime que lorsque la durée est la chose éprouvée — la pression longue du § 7.3, le retour automatique en lecture du § 8.4 — et l'étape avance alors l'horloge contrôlée du § 19.4 plutôt que d'attendre le temps réel.
Le parcours principal construit son événement, il ne charge pas une démonstration. C'est le parcours de l'opérateur. Un CSV ne reproduit pas une configuration (§ 10.3) ; une démonstration chargée court-circuite précisément les sept premiers écrans que le guide doit montrer. Les démonstrations livrées n'interviennent qu'aux étapes où une configuration déjà tendue est le propos.
| fichier | configuration | ce que la capture d'écran montre | ce que son étape affirme |
|---|---|---|---|
01-premier-ecran.png |
répertoire neuf | le répertoire vide : la commande de création et les configurations de démonstration offertes, jamais une liste vide (§ 8.1) | chaque configuration que le catalogue déclare est nommée et cliquable, et leur nombre est lu dans le catalogue, jamais écrit dans le scénario |
01b-passer-en-ecriture.png |
idem | la commande « Modifier » à place fixe, et le cadre de mode autour du plan (§ 8.4) | avant ce geste, une tentative de modification est refusée ; après, elle passe |
02-import-apercu.png |
participants_cas_limites.csv |
l'aperçu obligatoire (§ 10.1) : encodage et séparateur détectés et nommés, association par en-tête, nombre de lignes refusées | l'encodage et le séparateur affichés sont ceux que le module d'import a retenus ; l'aperçu n'est pas contournable |
03-import-rapport.png |
idem | le rapport : lignes valides, orthographes d'appartenance fondues avec leur compte, lignes refusées avec leur motif | au moins un groupe d'orthographes fondu et au moins une ligne refusée sont visibles |
04-grille-participants.png |
l'événement en construction | la grille éditable, le tri, le filtre, une personne exclue (§ 10.2) | le compte affiché égale le compte importé |
05-tables-etat.png |
idem, deux tables posées par le scénario | deux tables à 8 places dont l'une suit le défaut et l'autre porte une surcharge de même valeur (§ 6.1) | les deux états s'affichent différemment à valeur égale |
06-defaut-consequence.png |
idem | l'énoncé chiffré avant application — tables suivant le défaut, surchargées épargnées, total des sièges contre nombre de participants | le total annoncé égale celui que recalcule le moteur |
07-plan-tables.png |
idem | le plan, ses tables à l'échelle, l'échelle graphique, la mention « disposition relative » (§ 7.7) | la mention est présente tant qu'aucune dimension de salle n'est saisie |
08-animateurs-designes.png |
idem | un siège cadenas fermé + anneau (réservé, tous les tours) et un siège hachuré à cadenas ouvert (titre non pourvu), avec la liste numérotée qui énonce les deux états en toutes lettres (§ 7.6), et les deux listes de travail du § 5.8 | les marques sont des éléments SVG, aucun point de code emoji dans le sous-arbre ; aucune chaise ne porte plus de deux marques |
09-refus-sur-place.png |
idem | un refus du § 5.9 affiché sur la place concernée, nommant sa cause et le geste qui le lève | le refus photographié est l'un de ceux qui subsistent à la pose — k_t > c_t, une personne exclue, deux places sur un même tour, un dépôt sur une place réservée à un autre ; aucune boîte de dialogue n'est ouverte |
10-diagnostic.png |
grande démonstration | le diagnostic préalable (§ 5.6), le plafond a priori, les 99 retours imposés énoncés à part, et le plancher de l'écart d'itinéraire | les valeurs affichées égalent celles du moteur pour la même entrée ; le plafond des quatre animateurs des tables de 7 vaut 24 |
11-propositions.png |
l'événement en construction | plusieurs propositions comparées, l'ordre de classement écrit dans l'interface (§ 5.7), les colonnes de diversité | au moins trois propositions ; l'ordre affiché est celui que produit la clé de tri du moteur |
12-qualite-profil.png |
grande démonstration | la courbe en escalier, deux séries sur un seul axe, le plafond réalisé tracé dans le même ordre, le pire cas au premier rang (§ 12.3) | la page a dessiné plutôt que d'opposer son refus — c'est ainsi que la garde du § 12.3 montre qu'elle n'a pas été déclenchée |
13-qualite-collisions.png |
petite démonstration, variante « conflit inévitable » | les barres horizontales à deux segments, « prouvé inévitable » et « reste », en collisions cumulées, avec l'effectif, les paires distinctes et l'excédent en encre secondaire | le segment prouvé vaut 4 collisions cumulées et égale le plancher du diagnostic, au chiffre près ; les lignes sont triées sur l'excédent (§ 12.4) |
14-qualite-paires.png |
12 personnes, 4 tables de 3, 6 tours, construite | le tableau des paires répétées trié, pas la matrice (§ 12.5) | le tableau n'est pas vide, et chaque ligne mène au tour fautif |
15-pas-de-matrice.png |
grande démonstration | à 260 participants, le tableau et non la matrice, avec la phrase qui le dit | aucune matrice n'est dessinée |
16-minimum-atteint.png |
petite démonstration | « minimum atteint », les compteurs de collision et de répétition mesurés à zéro, la ligne secondaire « ancrés » à « — » (§ 15.3) | la mention est présente, les compteurs mesurés sont nuls, et la population vide affiche un tiret, jamais zéro |
17-comparaison-manque.png |
variante sans exception (§ 15.1) | les deux profils de manque qui se croisent, l'axe commun, le zéro libellé, les trois effectifs de rangs (§ 12.10.6) | la page écrit sa question en toutes lettres et les trois effectifs ; aucune échelle propre à une courbe |
18-comparaison-escalier.png |
idem | l'escalier du manque, jumeau tabulaire de la vue précédente | les effectifs cumulés du tableau reproduisent la courbe |
19-ajustement.png |
l'événement retenu | le déplacement d'une personne après la retenue, l'interface disant lequel des deux gestes est en cours (§ 5.8) | le libellé de l'état des données est visible |
20-mode-lecture.png |
idem | le cadre et le libellé permanents autour du plan, et le bandeau d'application (§ 8.4) | un geste de chaque famille — glisser une personne, glisser une table, tirer une poignée, poser une réservation — est tenté, et l'empreinte d'état est inchangée après les quatre |
21-historique.png |
idem | la liste des instants, le fil courant distingué des fils abandonnés, un jalon nommé (§ 8.3) | au moins un fil abandonné est présent et marqué comme tel |
22-plan-brouillon.png / 23-plan-retenu.png |
idem | l'aperçu d'impression du plan (§ 11.7) avec son cartouche, son échelle graphique et son horodatage, avec puis sans le filigrane BROUILLON | deux images, parce qu'une absence ne se photographie pas : le filigrane présent sur l'un, absent sur l'autre, et la paire est le seul exposé lisible de la règle |
24-badges-controle.png / 25-badges-planche.png |
idem | la feuille de contrôle avec son échelle de 100 mm et ses comptes, puis une planche de quatre A6 et ses trois filets de coupe | voir § 19.6 — ces deux images ne viennent pas du navigateur. La paire est nécessaire : la feuille de contrôle est ce que l'opérateur mesure sous une règle avant d'engager la rame, et une planche seule ne le montre pas |
Trois pièges de dénombrement que cette table évite, et qui sont la raison de sa forme.
- Le premier écran n'offre pas « deux démonstrations ». Le § 15 en livre quatre configurations. Une étape qui en affirme deux échoue, ou pire, fige un compte faux. Le compte se lit dans le catalogue.
- Les paires répétées ne sont pas garanties sur la grande démonstration — le § 12.5 donne le dénombrement. Le tableau se photographie sur une configuration où la répétition est forcée.
- Les collisions d'appartenance ne sont pas garanties non plus sur la grande : sa plus grosse appartenance compte 12 membres pour 33 tables. La variante « conflit inévitable » les force.
Les configurations qu'aucun catalogue ne porte sont construites, pas
cataloguées. Trois besoins sortent des quatre entrées du § 15 : les paires
répétées sur 12 personnes, 4 tables de 3 et 6 tours ; le témoin de pagination à 257
badges ; les cas dégénérés du § 12.6. Le scénario les construit, par des
scénarios courts, et le catalogue reste à quatre entrées — sans cette règle,
l'étape 01-premier-ecran change de valeur chaque fois qu'une épreuve a besoin
d'une configuration de plus.
Les cas dégénérés du § 12.6 se photographient à part, par des scénarios courts qui montent la configuration visée et vont droit à l'écran : aucun placement, une seule table, un seul tour, une seule appartenance, aucune collision, une personne en réserve, un plafond réalisé nul. La raison n'est pas la fragilité d'une chaîne longue : chacun exige une configuration différente, et un parcours linéaire n'en porte qu'une à la fois.
S'y ajoute, au même titre, le fichier participants_cas_limites_cp1252.csv
(§ 10.3) : la décision d'encodage en quatre temps du § 10.1 est le mécanisme dont
la défaillance est la plus muette — « Benoît » devient « Benoît » et aucun calcul
n'échoue — et c'est la raison pour laquelle le guide la montre.
La grande démonstration n'intervient qu'aux étapes où l'échelle est le propos. Le coût d'un parcours mené sur 260 personnes est dominé par la génération, et il est mesuré, non supposé (§ 14.1) : le seuil au-delà duquel une étape quitte le parcours principal est cette mesure.
19.4 Ce qui rend une capture d'écran reproductible
Une capture d'écran qui change à chaque exécution rend sa revue impossible : le diff est entièrement bruit, et la seule image qui comptait s'y noie. Le logiciel neutralise chaque source de variation nommément.
| source de variation | neutralisation |
|---|---|
| la règle d'arrêt de la recherche | un compte, jamais une durée (§ 5.10). C'est la source décisive, celle qui rendrait toutes les autres inutiles : une recherche bornée par le temps rend le placement dépendant de la vitesse de la machine, et la capture du plan de salle ne serait reproductible sur aucune autre machine que celle de référence |
| le placement engendré | une graine entière écrite dans le scénario, passée à la commande de génération ; à graine et entrée égales, le placement est identique |
| l'ordre d'itération et les comparaisons de chaînes dans le moteur | la règle du § 15.5, point 4, étendue du générateur au moteur : aucun localeCompare, aucun parcours reposant sur l'ordre des clés d'un objet, départage par identifiant entier |
| l'ordre des propositions à égalité | l'identifiant vient de la place dans la suite de graines dérivées, jamais de l'ordre d'achèvement (§ 5.7) : sinon le départage se fait par la vitesse de la machine, et deux exécutions classent deux propositions à égalité dans deux ordres |
| la taille du cadre d'affichage | la commande du standard dimensionne la fenêtre, pas le cadre d'affichage : la hauteur de la barre du navigateur diffère d'une version à l'autre, et un cadre laissé libre gouverne k_ajusté (§ 7.2) et donc le seuil de 24 px au-delà duquel les sièges cessent d'être des cibles (§ 7.3). Le scénario pose le rectangle de fenêtre, relit innerWidth et innerHeight, corrige, et échoue s'il n'obtient pas les valeurs visées |
| le facteur d'échelle de l'affichage | fixé au lancement du navigateur, pas par une commande du standard, qui n'en offre aucune |
| le mode d'affichage du navigateur | un seul mode, nommé, inscrit au manifeste : les métriques de texte en diffèrent |
| le cadrage mémorisé | le § 8.5 range le cadrage dans un fichier de réglages locaux : le scénario démarre sur un profil neuf, ce qui lui donne du même geste un dossier de travail neuf |
| le numéro d'ordre du nom de démonstration | charger une démonstration crée « ‹nom› 2 » à la deuxième exécution (§ 15) ; le répertoire neuf ramène chaque exécution au premier ordinal |
| l'horloge — horodatage d'historique, pied de page, « dernière modification », retour automatique en lecture | une seule source d'horloge dans toute l'application hors moteur, et elle possède aussi les minuteries. Un instant figé ne suffit pas : un compte à rebours bâti sur une minuterie du moteur d'exécution continue d'avancer sous une horloge gelée, et le retour automatique en lecture tombe au milieu d'une étape. Le scénario installe une horloge dont il contrôle l'avance, ce qui lui permet aussi d'éprouver les durées |
| le numéro de version affiché | une construction de documentation pose une marque de provenance à la place du numéro (§ 18.6), dans le bandeau comme au pied des pages imprimées et sur la feuille de contrôle des badges. Sans elle, le texte relevé de chaque capture change à chaque livraison, l'empreinte d'étape change avec lui, et toutes les images se réécrivent à chaque incrément — précisément le bruit que la réécriture conditionnelle du § 19.5 existe pour supprimer |
| le chemin affiché dans la liste des événements (§ 8.1) | racine de travail neutre, et le contrôle refuse une capture dont le texte relevé — celui de tous les nœuds de texte de la région photographiée, pas des seuls éléments que l'étape nomme — porte un chemin hors de cette racine. Un chemin réel porte un nom de compte, qu'une documentation publie |
| la police réellement rendue | la police embarquée (§ 11.1) est celle du pilotage. Le scénario mesure une chaîne témoin par getComputedTextLength() et la compare à la valeur enregistrée avec une tolérance relative, constante mesurée (§ 14.1) — exiger l'égalité exacte reproduirait, sur une mesure de texte, l'erreur que cette section refuse sur les pixels. La tolérance se fixe au-dessous de l'écart qu'introduit un repli de police, qui est d'un tout autre ordre |
| le thème | réglé par le réglage de l'application, non par une préférence système émulée (§ 19.2) |
| les transitions et animations | annulées par une feuille de style injectée. Une capture prise à mi-transition est une image différente à chaque exécution |
| l'horodatage interne du PDF | il vient de la même source d'horloge, donc de l'instant contrôlé |
| le tramage du rendu — lissage des glyphes, accélération matérielle | ne se neutralise pas d'une machine à l'autre. C'est la source que le logiciel ne prétend pas supprimer, et elle décide de tout ce qui suit |
Parce que le tramage ne se neutralise pas, la fraîcheur ne se contrôle jamais sur les pixels. Une empreinte d'image échouerait sur toute machine autre que celle qui a produit la référence ; un contrôle qui échoue partout se désactive, et il ne reste rien. Le contrôle porte donc sur l'état, pas sur l'image.
19.5 Le manifeste, et l'écriture conditionnelle
Chaque capture d'écran porte une entrée dans un manifeste engendré : identifiant d'étape, étape du § 2.1 à laquelle elle appartient, version réelle de l'application, graine, dimensions relues du cadre d'affichage, facteur d'échelle, mode d'affichage, thème, instant contrôlé, le texte relevé de la région photographiée, les styles calculés des marques porteuses d'information, et une empreinte canonique de l'état de l'événement au sens du § 8.8.
L'empreinte d'une étape est celle de son entrée de manifeste entière, l'image exclue — et non celle du seul état de l'événement. Les étapes 10 à 18 consultent le même état sous des vues différentes : une empreinte réduite à l'état leur donnerait à toutes la même valeur, et une régression de la page de qualité ne réécrirait jamais son image.
Le scénario ne réécrit un fichier image que si l'empreinte de son étape a changé. Sans cette règle, une exécution sur une autre machine réécrit toutes les images par le seul effet du lissage, le dépôt enfle à chaque passage et le diff cesse d'être lisible. Avec elle, une image qui change dans un diff signifie que l'état qu'elle montre a changé — c'est la propriété qui rend la revue possible.
Les styles calculés relevés servent deux fois : ils détectent une feuille de style qui ne s'est pas appliquée, et ils produisent le tableau des ratios de contraste que le § 14.5 exige mesurés sur la couleur relue du rendu, pour les deux thèmes. Ce tableau est engendré, pas recopié.
Une réserve que le style calculé impose, et qui devient une règle de conception : le style calculé rend la couleur résolue d'un élément, non le pixel composé. C'est pourquoi le § 14.5 interdit toute opacité partielle, tout filtre et tout mode de fusion sur le texte et sur les marques porteuses d'information.
19.6 Les images qui ne viennent pas du navigateur
La planche de badges est le seul PDF (§ 11.1), et le logiciel la construit depuis le modèle par une bibliothèque embarquée, jamais par le moteur de rendu. Une capture d'écran d'un aperçu, pour la montrer, exigerait une seconde implémentation de la mise en page — la double arithmétique que le § 13.2 interdit, transposée à la pagination, avec le même défaut : les deux dérivent et les tests des deux restent verts.
Le logiciel engendre donc ces images sous node, en tramant les pages du PDF
effectivement produit. Le rasteriseur est une dépendance de construction de la
documentation. Ce chemin est plus fort qu'un aperçu : l'image montre l'artéfact
livré, et non une approximation à l'écran. Sa fraîcheur se contrôle sur
l'empreinte canonique du PDF produit, que l'horloge contrôlée du § 19.4 rend
stable, et non sur les pixels tramés.
Le plan, lui, n'est plus un PDF : ses deux images viennent du navigateur, sur la vue d'aperçu d'impression du § 11.7, qui applique au document vivant la feuille de style d'impression réelle. Ce n'est pas une seconde mise en page : c'est la même, regardée avant d'être envoyée à l'imprimante.
Le plan à l'écran reste une capture ordinaire du navigateur : il est la surface de travail, et c'est à ce titre que le guide le montre.
19.7 Comment une capture d'écran périmée se détecte
Une documentation dont les images montrent une version antérieure est pire qu'une documentation sans image : elle fait croire au lecteur qu'il regarde le logiciel qu'il a sous les yeux. Trois contrôles, à trois moments, et le moment compte autant que le contrôle.
Au niveau node, à chaque modification (§ 14.4), sans navigateur :
l'appariement. Toute image référencée par la documentation a son entrée au
manifeste, et toute image présente est référencée. Une orpheline est un échec dans
les deux sens. S'y ajoute le contrôle d'ordre du § 19.3 : une étape du § 2.1 sans
capture, ou des captures dont l'ordre contredit celui des étapes, font échouer la
construction. Ces contrôles passent sur une machine sans navigateur, à toute heure,
et c'est pourquoi ils sont à ce niveau — bornés à leur périmètre, dépendances
et sorties de construction exclues (§ 14.14).
Au niveau node, à la construction d'une livraison : la version. La version
réelle inscrite au manifeste égale celle que la construction produit. Ce contrôle
ne descend pas au cycle de chaque modification : le manifeste n'est réécrit que
par une exécution en navigateur, donc un simple incrément de version le rendrait
rouge à chaque modification jusqu'à ce que quelqu'un relance le pilotage. Un
contrôle qui échoue en permanence se désactive. Au cycle court, l'écart de version
est affiché, nommé et non bloquant ; à la livraison, il bloque.
Au niveau navigateur, avant chaque livraison : le scénario rejoue le parcours et compare, étape par étape, l'empreinte d'étape et le texte relevé à ceux du manifeste. Un écart nomme l'étape et la nature de l'écart.
Ces contrôles refusent de passer sur un balayage vide (§ 14.2) : « zéro capture examinée » n'est pas « zéro capture périmée ». Et chacun est montré en train d'échouer avant d'être retenu — une image volontairement remplacée par une version antérieure doit faire échouer la construction.
Ce que le contrôle d'état ne voit pas, et il faut le dire : une régression purement visuelle à état identique. Les styles calculés relevés en attrapent une partie — une couleur d'état devenue grise, un contraste tombé sous le seuil — mais une image mal cadrée, une table qui déborde ou un texte qui se chevauche passent. La relecture humaine du § 16.1 reste la seule garde.
19.8 Ce qui est engendré, ce qui est écrit à la main
La frontière passe là où une divergence est mécaniquement détectable.
Écrit à la main, jamais engendré : toute phrase qui décrit une fonctionnalité, qui dit pourquoi elle existe, et qui dit ce qu'un écran veut dire. Un texte explicatif engendré à partir du code décrit ce que le code fait, ce que le lecteur voit déjà sur l'image, et jamais ce qu'il cherche. Les légendes des captures d'écran sont également manuelles : nommer ce qu'il faut regarder dans une image est un jugement.
Engendré :
| contenu | source unique |
|---|---|
| les captures d'écran et leur manifeste | le scénario, et pour les images de la planche, la construction sous node |
| le tableau des réglages et de leurs valeurs par défaut | la même définition que lit l'interface |
| le tableau des colonnes CSV reconnues et de leurs synonymes (§ 10.1) | le module d'import |
| la liste des commandes et de leurs libellés d'historique (§ 8.2) | le registre des commandes |
| les raccourcis clavier | la table des raccourcis |
| le tableau des ratios de contraste, par thème (§ 14.5) | les styles relevés au pilotage |
| les grandeurs des démonstrations et leurs plafonds a priori (§ 15.1) | les fichiers de démonstration livrés, lus, jamais recopiés |
| le tableau des constantes mesurées (§ 14.1) | le banc du § 19.10 |
| le numéro de version, partout où il paraît, nom de l'exécutable compris | version.json (§ 18.3) |
Un tableau recopié à la main diverge du logiciel en un commit et personne ne s'en aperçoit : c'est le même mode de défaillance que la double arithmétique du § 13.2, transposé à la prose.
Le mécanisme d'insertion. Les sources de documentation portent des marqueurs nommés ; la construction remplace le contenu entre marqueurs et ne touche à rien d'autre. Elle dispose d'un mode de vérification qui échoue quand une région engendrée diffère de ce qu'elle produirait : une correction faite à la main dans une région engendrée fait échouer la construction au lieu d'être perdue au passage suivant (§ 14.3).
GUIDE-WINDOWS.md sort de la boucle (§ 16.1). Le pilotage s'exécute sur
web sous Linux ; les écrans que ce guide décrit — obtenir l'exécutable, le
premier lancement, le blocage par un antivirus, l'emplacement des fichiers dans
l'explorateur — appartiennent à electron sous Windows et aucun scénario ne les
produit. Le guide les décrit en toutes lettres, ou les illustre par des images
faites à la main qui portent la mention de leur origine. Prétendre les engendrer
livrerait des images de Linux dans un guide Windows.
19.9 La section sur l'algorithme
Son lecteur est l'opérateur. Il l'ouvre avec deux questions, toujours les mêmes : pourquoi deux propositions issues du même plan diffèrent, et pourquoi relancer aide parfois et jamais d'autres fois. La section répond à ces deux questions et s'arrête.
Ce qu'elle contient, dans cet ordre.
- Le problème, en une phrase : asseoir chacun à chaque tour de sorte que le plus mal servi rencontre le plus de monde possible.
- Pourquoi il n'a pas de réponse évidente, par un compte, pas par un mot savant. Douze personnes se répartissent sur quatre tables numérotées de trois de 369 600 façons pour un seul tour — 12! divisé par 6 quatre fois — et sur quatre tours le nombre dépasse 10²². Le logiciel n'essaie pas toutes les combinaisons, et la petite démonstration est le plus petit cas du catalogue. Ce compte est vérifiable et remplace avantageusement toute affirmation sur la difficulté du problème.
- Comment le logiciel cherche : il part d'un placement complet dont le point
de départ dépend de la graine, échange des personnes entre tables, garde
ce qui améliore, et s'arrête sur un compte de mouvements sans amélioration
(§ 5.10). D'où la réponse à la première question : deux recherches parties
de deux points différents s'arrêtent sur deux placements différents, et aucun
des deux n'est « le bon ». Cette phrase est vérifiable, et elle est
vérifiée : un test sous
nodeaffirme que deux graines donnent deux résultats et qu'une même graine donne deux fois le même. Un énoncé vérifiable vieillit mieux qu'un énoncé vague : si le moteur est réécrit et que la phrase cesse d'être vraie, le test le dit. - Ce que les contraintes font : chacune est un objectif, pas un interdit (§ 5.3). Quand elles ne tiennent pas ensemble, le logiciel produit le meilleur compromis et dit lequel, au lieu de refuser et de ne rien montrer.
- Le plafond, expliqué sur la petite démonstration : quatre tours, deux voisins par tour, huit rencontres au plus sur onze personnes. Personne ne peut faire mieux, et ce n'est pas un défaut du logiciel. Puis le même calcul sur une table de huit — quatre tours, sept voisins, 28 — et le cas des quatre animateurs des tables de sept de la grande démonstration, plafonnés à 24 : la relance ne déplace jamais un plafond.
- Ce que « minimum atteint » signifie : le logiciel l'écrit quand la personne la plus mal servie atteint ce que l'arithmétique autorise au mieux, mesuré contre le plafond a priori (§ 5.5) — raison pour laquelle la grande démonstration ne le décerne jamais. Relancer alors ne sert plus à rien. Et ce que son absence ne signifie pas : « atteignabilité inconnue » dit que le logiciel ne sait pas si mieux existe, pas qu'il existe.
- Ce que relancer ne peut pas : effacer une collision portée au segment « prouvé inévitable » (§ 12.4), ni rendre une table qui n'existe pas. Le diagnostic (§ 5.6) dit avant la recherche ce qui est hors d'atteinte et quel changement de configuration le lèverait — une table de plus, un tour de moins, une appartenance redistribuée.
- Comment comparer deux propositions : par le manque, et pourquoi les nombres bruts ne se comparent pas dès qu'une table est incomplète (§ 12.10.1). La section explique les trois chiffres de l'en-tête et, surtout, pourquoi un manque nul sur un itinéraire bas n'est pas une bonne nouvelle.
- Une lecture de l'écart entre propositions d'une même génération, donnée pour ce qu'elle est : un indice, pas une preuve. Quand la meilleure et la pire proposition d'une même génération portent le même minimum, c'est le plus souvent qu'une borne le tient — et la tuile du § 12.2 qui affiche le plafond à côté du minimum le dit en un coup d'œil, là où l'écart ne fait que le suggérer.
Ce qu'elle ne contient pas : aucun pseudo-code, aucun nom de fonction, aucun nom de module, aucune complexité asymptotique, aucun nom de famille d'algorithmes. Ces éléments n'apprennent rien à l'opérateur, se périment à la première réécriture du moteur, et font croire au lecteur que le texte ne lui est pas destiné — après quoi il ne lit plus la section, qui est la seule réponse écrite à la question qu'il posera.
La section s'appuie sur les captures d'écran 10, 11, 12, 16, 17 et 18 :
le diagnostic, les propositions classées, le profil trié, « minimum atteint » sur
la petite démonstration, et les deux vues de comparaison. Le contraste de la
variante « conflit inévitable » (§ 15.3) — une personne déplacée d'une appartenance
à l'autre, et la mention bascule — est le plus court exposé de toute la section, et
il tient en deux images.
19.10 Le banc de mesure
La spécification exige à plusieurs endroits un seuil « mesuré » — le nombre de
sièges au-delà duquel le dessin cesse d'être lisible (§ 6.2), la taille de
caractère en deçà de laquelle le plan se découpe en pages (§ 11.7), le seuil de
participants sous lequel la matrice se dessine (§ 12.5), la largeur de troncature
des noms longs (§ 12.6), le seuil de mouvement qui sépare un clic d'un glisser
(§ 8.4), les constantes de géométrie du texte (§ 14.1), le nombre de valeurs
distinctes de manque sous lequel la courbe cède la place à une phrase (§ 12.6), le
seuil de zoom qui retire les listes de noms (§ 17, point 6). Le niveau navigateur
est le seul endroit qui le peut, puisqu'une mesure sur le rendu réel est
précisément ce que jsdom ne sait pas faire (§ 14.4).
Le logiciel porte donc, au même niveau que le scénario et avec le même cadre d'affichage fixé, un banc de mesure qui produit un tableau de constantes avec, pour chacune, ce qu'elle mesure, sa valeur et la configuration sur laquelle elle a été relevée (§ 14.1). Ce tableau est une région engendrée de la documentation.
Deux mesures qu'il faut distinguer :
- Le seuil de 24 px du § 7.3 — en deçà duquel les sièges cessent d'être des cibles — est une géométrie. Deux captures d'écran l'encadrent délibérément, à cadre d'affichage fixé, et la documentation montre la bascule.
- Le seuil de zoom qui retire les listes de noms est un temps par image.
Aucune capture d'écran ne le mesure : une image est muette sur la durée qui l'a
produite. Le banc relève le temps par image pendant un déplacement de vue à
plusieurs niveaux de zoom, sur la grande démonstration et ses 33 tables avec
listes, et c'est cette courbe qui fixe la constante. La mesure est permise ici :
le § 14.7 interdit
performance.nowdans le moteur et dans le générateur, pas dans un banc.
Le même banc répond au § 17, point 4 : le coût de la maximisation du plafond a
priori sur les itinéraires admissibles, annoncé immédiat pour une dizaine de tables
et jamais mesuré sur 33 tables et 4 tours, se relève sous node sur la grande
démonstration et sa variante.
Deux mesures lui échappent et appartiennent au niveau manuel : le décalage des imprimantes visées et leur marge non imprimable (§ 11.3, § 14.1). Un banc de navigateur ne mesure pas une imprimante.
19.11 Ce que le pilotage attrape, et ce qu'il n'attrape pas
Un scénario de captures d'écran n'est pas une suite de tests, et le confondre avec elle donne une couverture imaginaire.
Ce qu'il attrape réellement :
- un écran devenu inatteignable, un bouton renommé, une étape du parcours qui n'existe plus — c'est-à-dire un guide d'usage devenu faux ;
- une page qui ne se dessine pas, une erreur non rattrapée, une promesse rejetée sans gestionnaire, un échec de construction de l'interface ;
- une tentative de requête hors de la boucle locale, que le § 2 interdit ;
- un libellé manquant — une clé affichée telle quelle, un élément vide ;
- un style qui ne s'applique pas, et un contraste tombé sous le seuil du § 14.5 ;
- une capture d'écran périmée, par le § 19.7 ;
- un chemin portant un nom de compte affiché dans l'interface.
Ce qu'il n'attrape pas :
- un libellé écrit en dur. Comparer le texte affiché au texte attendu ne distingue pas une chaîne venue de la table de traduction d'une chaîne écrite dans le composant. Le § 14.6 se garde par deux autres moyens, tous deux hors du parcours.
- la justesse d'un placement. Elle s'éprouve sous
node, sur le moteur pur. Un parcours vert sur un moteur faux est exactement aussi vert. En particulier, que la petite démonstration atteigne son minimum est une propriété du moteur : l'étape16la photographie, elle ne l'établit pas. - le contenu d'un PDF. Le § 11.1 l'éprouve en extrayant la couche de texte sous
node. L'image tramée du § 19.6 montre une mise en page, elle ne dit rien des accents présents dans le fichier produit. - la géométrie du pointeur dans ses modes de défaillance. Le pilote produit des
événements que le navigateur traite comme réels, donc un glissement se conduit et
se photographie — et le comportement du seuil du § 8.4 s'éprouve bel et bien.
Ce qui échappe est ailleurs : la valeur du seuil, qui relève du banc ;
pointercancel, qui n'arrive que d'un geste système ou d'une paume ; ettouch-action, qui ne se juge que sous un vrai doigt. Ce sont les pannes que le § 7.3 nomme, et elles restent au niveau manuel. - les garanties de panne du § 8.8. Elles sont des propriétés de
l'implémentation
electron; le pilotage s'exécute surweb. Aucune capture d'écran n'établit quoi que ce soit sur la résistance à une panne. - la lisibilité. Une image nette d'un plan illisible est une image nette.
- la justesse du français, et le fait qu'une phrase du guide dise bien ce que l'écran fait.
- le fait que le parcours soit celui de l'opérateur. Un scénario vert prouve que le script et le logiciel s'accordent. Qu'ils décrivent tous deux la bonne soirée reste la charge du niveau manuel (§ 14.4) et de la relecture par quelqu'un qui n'a pas écrit le logiciel (§ 16.1).
La règle qui empêche le faux vert : une étape qui ne trouve pas son élément échoue, elle ne passe pas son tour ; une exécution qui produit zéro capture d'écran échoue ; une construction dont une étape a échoué ne republie pas les images du passage précédent ; et chaque étape ajoutée est montrée en train d'échouer sur le code d'avant (§ 14.2). Un scénario qui avale ses échecs pour « toujours produire les images » rend exactement le service inverse de celui qu'on lui demande.