# Gestion table tournante Libre — spécification Exécutable livré : `gestion_table_tournante_libre_v___.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 : 1. le geste achevé est une entrée d'historique **et** un état sur le disque, jamais l'un sans l'autre (§ 8.2) ; 2. il se reprend par un retour à un instant, et le journal ne se tronque jamais (§ 8.3) ; 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 ».** 1. *Par certificat* — une proposition **égale** le plafond. Ce chemin ne suppose rien. 2. *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 sous `C − 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) − n` est **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 ni `n` ni `k_t` ; déplacer une réservation décrémente un `k_t` et 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 augmentent `n` ou 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 `` 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 `` : 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 `` ; **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 ``**, 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 `` n'a **aucun retour à la ligne** : chaque ligne d'une liste est un `` à 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 : 1. 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). 2. Si ce dossier se trouve sous un emplacement de **données applicatives par utilisateur** — `AppData` et 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 sous `AppData` y 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). 3. 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. 4. 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. 5. 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 | |---|---|---| | `.gtt.json` | l'état courant, complet | réécrit en entier à chaque entrée | | `.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/.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_version` fait é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_version` diffè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 : 1. **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. 2. **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. 3. **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. 1. **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. 2. **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. 3. **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. 4. **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 colonne `nom`. **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 : 1. **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. 2. **L'appartenance collante** : le champ conserve sa dernière valeur. Saisir les sept personnes d'une organisation, c'est taper sept noms. 3. **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 `electron` sait 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 plateforme `web`, 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 ``, ni ``, ni `` à 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 : 1. **Ce placement est-il bon ?** — reste-t-il à gagner en relançant ? 2. **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`, `role` et `aria-label` fonctionnent 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 | | PDF | `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 `` 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.** 1. **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. 2. **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. 3. **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 : 1. **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. 2. **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é 1. **Un seul générateur pseudo-aléatoire**, à état entier et transition explicite, accompagné d'un **vecteur de test** qu'un test rejoue. 2. **Une graine entière écrite dans la définition**, pas enfouie dans le code. 3. **L'ordre des tirages fait partie du contrat** : insérer un tirage au milieu décale tout ce qui suit. 4. **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). 5. **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_version` la 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é. 6. **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. 1. **TRANCHÉ — le format de stockage des placements.** La saisie reste en champs nommés, les placements engendrés passent à la **forme positionnelle** : § 8.9. 2. **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, table `ToUnicode` é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. 3. **TRANCHÉ — la désignation des animateurs est manuelle.** Il n'existe aucune commande qui les répartit : § 5.8 et § 2.1, étape 6. 4. **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 `node` sur 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é. 5. **TRANCHÉ — la comparaison trace le manque.** § 12.10, et sa configuration témoin est la variante sans exception (§ 15.1). 6. **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. 7. **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___.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 : ```json { "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 : ```markdown ## 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 : 1. la version technique de `package.json` n'est pas celle que la dérivation du § 18.1 produit depuis `version.json`, ou ne se redécompose pas en la version affichée ; 2. le module engendré ne reproduit pas, caractère pour caractère, ce que le script réengendre ; 3. le titre de la première section du `CHANGELOG.md` n'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 ; 4. les sections du changelog ne sont pas en ordre strictement décroissant, ou deux sections portent la même version ; 5. 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 ; 6. 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 dans `package.json` et le module engendré ; 7. 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 ; 8. 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 : 1. **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. 2. **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 `web` sous Linux et `electron` sous Windows — sortent de l'image, et le guide d'usage reste vrai sur la plateforme livrée. 3. **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. 4. **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 de `console.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.** 1. **Le problème, en une phrase** : asseoir chacun à chaque tour de sorte que le plus mal servi rencontre le plus de monde possible. 2. **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. 3. **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 `node` affirme 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. 4. **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. 5. **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.** 6. **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. 7. **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. 8. **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. 9. **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.now` dans 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'étape `16` la 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 ; et `touch-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 sur `web`. **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.