gestion_table_tournante_libre/spec.md
Mathieu Benoit 41e24fb08e [UPD] spec: events, reservations, diversity, quality page, Svelte
The specification covered the placement and little else; it now covers
the session where an operator spends an evening — several events,
automatic saving, history, a read-only default.

Three points decide the design rather than describe it: a title belongs
to a place and never to a person, so removing someone leaves the role
visible instead of silently vacant; the read/write mode is orthogonal to
the four states, not a fifth; a perfect reduced instance does not prove
the complete ceiling, which an enumeration establishes.

Checked: 29 tables of 8 and 4 of 7 seat exactly 260, and four
facilitators cap at 24 — a minimum no rerun improves.

--- FR ---

[UPD] spec : événements, réservations, diversité, page qualité, Svelte

La spécification couvrait le placement et peu d'autre chose ; elle
couvre désormais la séance où un opérateur passe une soirée — plusieurs
événements, enregistrement automatique, historique, lecture par défaut.

Trois points décident de la conception au lieu de la décrire : un titre
appartient à une place et jamais à une personne, si bien que retirer
quelqu'un laisse le rôle visible plutôt que vacant en silence ; le mode
lecture/écriture est orthogonal aux quatre états, non un cinquième ; un
réduit parfait ne prouve pas le plafond complet, ce qu'établit une
énumération.

Vérifié : 29 tables de 8 et 4 de 7 asseyent exactement 260, et quatre
animateurs plafonnent à 24 — un minimum qu'aucune relance n'améliore.

Assisted-by: Claude Opus 5
2026-10-05 05:04:37 -04:00

89 KiB
Raw Blame History

Gestion table tournante Libre — spécification

Exécutable livré : gestion_table_tournante_libre_v#_#.exe


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.

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 : un identifiant long quadruplerait 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 numéro à 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.

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 est sa liste de travail
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
paires de même appartenance paires distinctes assises ensemble
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

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.

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 noms. Le plafond dépend de l'itinéraire :

  • le plafond a priori, maximisé sur les itinéraires admissibles, affiché par le diagnostic avant la recherche ;
  • le plafond réalisé, calculé sur l'itinéraire de la proposition, affiché à côté de la mesure.

Sans ce partage, le diagnostic annonce 28, la page de qualité affiche 27, et l'opérateur conclut à une régression du moteur.

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.

Quand aucun chemin ne conclut, le logiciel affiche l'écart 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 paires de collègues » ;
  • 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.

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 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 : minimum de personnes rencontrées, puis paires de même appartenance, puis rencontres répétées, puis redondance d'appartenance comme départage. Ce 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.

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.

Une commande assistée, « Poser les animateurs », prend les participants portant un titre pressenti donné et crée pour chacun une réservation de portée « tous les tours » et un titre sur la place visée. Un geste, une entrée d'historique, tous les refus évalués avant d'appliquer quoi que ce soit.

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 : refusé, avec le nombre de places manquantes.
  • 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.

Le logiciel éprouve l'incrémental contre le recalcul complet sur chaque configuration de démonstration.


6. Les sièges d'une table

6.1 Un défaut d'événement, des surcharges par table

L'événement porte un nombre de sièges par défaut. Toute table créée suit ce défaut ; elle n'en garde pas une copie. Une table peut recevoir une surcharge et cesse alors de le suivre.

état d'une table ce que cela veut dire
suit le défaut sa capacité vaut le défaut et change avec lui
surchargée sa capacité est fixée à la main, le défaut ne l'atteint plus

Réaligner n'est pas un troisième état : c'est la commande qui retire une surcharge. L'interface montre l'état, pas seulement la valeur — une table à 8 qui suit un défaut de 8 et une table surchargée à 8 affichent le même nombre et réagissent différemment au prochain changement.

Changer le défaut n'écrase aucune surcharge. Une saisie manuelle table par table disparaîtrait sinon sans trace, et l'écart ne se découvrirait qu'à l'impression.

Le logiciel énonce la conséquence chiffrée avant de l'appliquer :

« 9 tables suivent le défaut et passent de 8 à 10 places ; 4 tables surchargées (7, 7, 6, 12) ne changent pas. Total : 104 → 122 sièges pour 118 participants. »

Deux boutons : appliquer, ou appliquer et réaligner les 4 surchargées — le second geste est séparé parce qu'il détruit de la saisie.

Un changement de défaut est une seule entrée d'historique, quel que soit le nombre de tables touchées. Baisser le défaut subit les refus du § 6.2, et le logiciel les évalue toutes avant d'en appliquer aucune : si une seule table refuse, l'opération entière est refusée, nommément, et rien ne change.

6.2 Ce que le logiciel refuse

  • Moins de 2 sièges. Une table à 1 place est un participant qui ne rencontre personne de la soirée — c'est ce que le logiciel existe pour éviter.
  • Une capacité inférieure au nombre de réservations portées par la table.
  • Une réduction qui retire un siège portant une réservation ou un titre — les sièges disparaissent par numéro décroissant ; le refus les liste.
  • Une capacité non entière, négative, ou saisie en texte.

Deux cas avertissent sans refuser : réduire sous le nombre de personnes assises (les surnuméraires vont dans la réserve visible, nommément listées, jamais effacées en silence) ; et dépasser le nombre de sièges au-delà duquel le dessin cesse d'être lisible — seuil mesuré au sens du § 14.1.


7. Le plan de salle

C'est la surface de travail, du début à la fin. Il est dessiné en SVG et l'opérateur agit directement dessus.

7.1 Les gestes

geste effet
cliquer une chaise sélectionne la place — l'assigner, la réserver, lui donner un titre
glisser une personne sur un siège libre la déplace
glisser une personne sur une autre les échange
glisser une personne hors des tables la retire du tour ; elle attend dans la réserve visible
glisser une table change sa position
tirer la poignée d'une table change son nombre de places
survoler une table en conflit affiche le détail des règles violées
zoomer, déplacer la vue change le cadrage

Un menu par personne offre les mêmes actions sans glisser, pour le tactile et le clavier.

La poignée pilote le nombre de places, jamais une taille libre. Chaque table est dessinée à l'échelle de son nombre de sièges ; une taille libre permettrait un dessin qui ment sur la capacité, et ce mensonge n'apparaîtrait sur aucun indicateur. En tirant, l'opérateur fait défiler des nombres entiers ; les chaises apparaissent et disparaissent sous le pointeur.

La poignée refuse de descendre sous le nombre de sièges occupés, tous tours confondus — sinon un geste de géométrie désassoit deux personnes au troisième tour, que l'opérateur ne regarde pas. Le logiciel nomme le nombre bloquant et le tour concerné.

La rotation d'une table carrée n'existe pas dans la première itération : elle introduirait une orientation dans la numérotation des sièges et dans la position de la liste de noms. Le logiciel n'offre aucune poignée de rotation, plutôt qu'une poignée qui dérange la numérotation.

7.2 Les coordonnées

C'est l'endroit où un plan interactif se casse, silencieusement, et seulement quand on a zoomé.

Le <svg> racine ne porte pas de viewBox, ni bordure ni remplissage. Son unité est donc le pixel CSS. La formule qui répartit la position du pointeur dans la largeur d'un viewBox est fausse dès que les rapports diffèrent : le moteur de rendu centre le dessin, laisse des bandes vides, et le plan répond juste au centre et faux sur les bords. Supprimer la cause vaut mieux que corriger l'effet.

L'état de vue est trois nombres : k, l'échelle en pixels par centimètre, et tx, ty en pixels. Le plan vit dans un unique groupe <g transform="translate(tx,ty) scale(k)"> : zoomer écrit un attribut sur un élément.

La conversion est une fonction pure, donc éprouvée sous node :

dessin_depuis_ecran(vue, cadre, xEcran, yEcran) =
    { x: (xEcran − cadre.gauche − vue.tx) / vue.k,
      y: (yEcran − cadre.haut   − vue.ty) / vue.k }

Trois invariants la rendent vraie, et chacun est une épreuve : pas de viewBox ; ni bordure ni remplissage sur le <svg> ; aucun ancêtre portant une transformation CSS — elle déplacerait le rectangle sans déplacer les unités, et le plan répondrait faux partout.

Une épreuve en navigateur confronte la fonction pure à getScreenCTM().inverse(). Ce n'est pas une seconde implémentation : c'est un oracle fourni par le moteur de rendu.

Le zoom se fait sous le curseur, sinon le contenu fuit vers un coin et l'opérateur poursuit la table qu'il visait.

Les bornes du zoom sont relatives, jamais absolues : k reste entre 0,5 × k_ajusté et 10 px/cm. Une borne basse absolue est une erreur démontrable — sur un portable de 1 366 × 700, un plan de 36 m s'ajuste à k ≈ 0,19 px/cm, et un plancher fixé à 0,2 rendrait l'ajustement à la fenêtre impossible sur la configuration de démonstration elle-même.

Le plan vide — aucune table — n'a pas de rectangle englobant : k_ajusté vaut 1 px/cm, l'origine est centrée. Sans cette règle, l'ajustement divise par zéro et rend k = Infinity, que le transform accepte sans rien dessiner et sans rien signaler.

Un glissement se calcule en écart, jamais en position absolue — sinon la table saute au premier mouvement pour mettre son origine sous le pointeur.

Le logiciel ne lit ni getBBox() ni getScreenCTM() dans un pointermove : ces appels forcent un recalcul de mise en page au milieu de la boucle d'images.

7.3 Le pointeur et le tactile

La capture du pointeur est obligatoire, et elle porte sur la racine <svg>, pas sur la table. Sans capture, un mouvement rapide sort le curseur, les événements vont à ce qui est dessous, le pointerup n'arrive jamais et la table reste collée au curseur. Et si le composant remplaçait l'élément capturant pendant le glissement, la capture serait perdue — raison pour laquelle le logiciel écrit l'attribut transform directement pendant le glissement et ne confie le résultat au modèle qu'au pointerup.

Pendant une capture, ev.target désigne l'élément capturant, pas ce qui est sous le pointeur. Le logiciel ne cherche donc pas la cible d'un dépôt dans le DOM : il convertit le point en coordonnées de dessin et demande au module de géométrie quel siège le contient. Le dépôt devient une fonction pure, éprouvable sous node, qui ne peut pas diverger du dessin puisque c'est elle qui place les chaises.

pointercancel est une annulation, pas une fin : le logiciel remet la table où elle était. La touche d'échappement et le passage en lecture font de même.

Le tactile. touch-action: none sur le plan, sans quoi le navigateur interprète le glissement comme un défilement avant de livrer le moindre pointermove — la panne tactile la plus fréquente, et la plus déroutante puisqu'à la souris tout marche.

La taille des cibles dépend du zoom, et la règle doit le dire. Deux sièges voisins d'une table de 8 sont à 60 cm ; une cible de 44 px exige k ≥ 0,73 px/cm, le plancher de 24 px exige k ≥ 0,40 — tandis que l'ajustement d'une salle entière donne k ≈ 0,2 à 0,45. Au cadrage le plus utile, un siège n'est pas pointable au doigt. La règle est donc : sous le zoom où la cible tomberait en deçà de 24 px, les sièges cessent d'être des cibles et la table devient la cible, le siège se choisissant dans son panneau. Le logiciel le montre en cessant de dessiner les chaises individuellement, plutôt qu'en laissant l'opérateur manquer sa cible sans comprendre.

Il n'y a pas de survol au doigt : le détail d'un conflit est accessible par survol, par focus clavier et par pression longue — un seul composant d'infobulle, trois déclencheurs.

Un seul écouteur sur la racine, la cible étant identifiée par closest('[data-siege]'). Les mouvements sont regroupés par image.

Le clavier n'enfile pas 264 arrêts de tabulation : le plan est un seul arrêt, les flèches passent d'une table à l'autre, l'entrée descend dans ses sièges, l'échappement remonte.

7.4 Le texte

<text> n'a aucun retour à la ligne : chaque ligne d'une liste est un <tspan> à décalage vertical explicite. La largeur se mesure par getComputedTextLength() sur la police rendue ; la hauteur de ligne se mesure sur un gabarit portant capitales accentuées et jambages — « Ôjgq », « Æ », « W » — jamais une chaîne moyenne, jamais déduite de la taille de police.

Le logiciel n'emploie pas textLength : cet attribut comprime les glyphes et produit un nom déformé plutôt qu'un nom tronqué.

La troncature se décide par document, pas par support :

document règle
le plan de salle, à l'écran comme au PDF abrège avec une marque, la largeur d'un bloc de noms étant la même contrainte des deux côtés
la fiche, la feuille par table, la liste d'accueil ne tronquent jamais : elles replient

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 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.

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.

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é et ses exports, la comparaison des propositions, la lecture de l'historique, l'impression. 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 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.

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 réglage local de la machine indexé par l'identifiant de l'événement. É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.

8.6 Les fichiers

Emplacement : un dossier nommé d'après le produit, dans les Documents de l'opérateur — pas dans un répertoire de données caché. Il doit pouvoir copier un événement sur une clé, le joindre à un courriel, le restaurer depuis sa sauvegarde, sans le logiciel.

Deux fichiers par événement :

fichier contenu écriture
<nom>.gtt.json l'état courant, complet réécrit en entier à chaque entrée
<nom>.gtt-journal.jsonl les entrées d'historique ajout en fin, une ligne par entrée

Les deux portent l'identifiant interne de l'événement, et c'est lui qui les apparie — jamais leurs noms.

Le nom du fichier dérive du nom de l'événement — un dossier d'identifiants opaques est inutilisable dans un explorateur. La dérivation retire les caractères que Windows refuse, refuse les noms réservés quelle que soit la casse, compare les collisions sans égard à la casse (le système de livraison y est insensible, celui de développement non), borne le chemin complet sur le plus long des deux suffixes (sinon l'état tient et son journal déborde), et retombe sur un nom générique quand la dérivation ne laisse rien. 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.

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 browser, 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 et sa révision ; l'analyse syntaxique seule ne distingue pas un fichier complet d'un fichier plausible.
  • Sérialisation canonique : ordre de clés fixé, aucune valeur dérivée de l'horloge ou d'un tirage. Sans elle, deux états identiques produisent deux fichiers différents et un correctif enregistre du bruit à chaque geste.

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.


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 ; 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é ; sinon décodage UTF-8 strict, et repli sur windows-1252 en cas d'échec.

L'étape 3 n'est pas une précaution de principe : un texte UTF-16 dont le contenu est latin est valide en UTF-8, chaque octet nul s'y décodant comme le caractère nul. Un ordre à trois temps produirait des noms entrelardés de caractères nuls, qui ne font échouer aucun calcul. Le mode de défaillance est muet : « Benoît » importé en « Benoît » se découvre 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.

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. Sur 260 lignes, un refus global coûterait l'import entier pour une faute en ligne 213.

Trois cas restent des refus globaux : aucun en-tête reconnaissable, aucune colonne associée à nom, zéro ligne valide.

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, et une ligne refusée faute de nom.

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 bibliothèque embarquée, jamais l'impression du navigateur

Le logiciel produit ses PDF en mémoire, par une bibliothèque embarquée.

L'argument décisif est l'épreuve. Une bibliothèque embarquée s'exécute sous node : un test de quelques secondes affirme qu'une feuille de table tient sur une page, que 260 fiches comptent le nombre de pages attendu, et que les accents sont présents — en extrayant la couche de texte du PDF produit, pas en regardant une image. L'impression du navigateur exige de démarrer l'application et un moteur de rendu : elle sort du cycle « à chaque modification », et un contrôle qui ne tourne qu'avant une livraison ne tourne pas.

S'y ajoute que la pagination de l'impression dépend des règles CSS, dont le comportement diffère d'un moteur à l'autre : l'opérateur découvre le saut de page au milieu d'une table une fois la feuille sortie.

Conséquences à tenir :

  • La police est embarquée dans le PDF, couvrant le latin étendu du français, ligatures comprises. Un test échoue si un caractère d'un nom d'épreuve ne se retrouve pas dans la couche de texte.
  • Le PDF se construit depuis le modèle, jamais depuis le DOM : l'écran et la feuille lisent la même source.
  • Le plan s'exporte en vectoriel. Le SVG inséré est la forme d'export, avec son viewBox calculé — que le document vivant n'a pas — et il porte le cadrage ajusté à la page, non celui de l'écran. Sans le viewBox, un SVG autonome se rend à 300 × 150 pixels ; sans le cadrage de page, deux impressions du même plan diffèrent selon le zoom laissé à l'écran.
  • Le rendu du plan s'interdit tout élément HTML enchâssé dans le SVG, que le PDF ne sait pas reprendre.

À confirmer dans la première semaine : que la bibliothèque accepte le SVG que le plan produit, avec ses <use>, ses <symbol> et ses <tspan> à décalage explicite. C'est le seul point de l'architecture de sortie qui repose sur une supposition.

11.2 Ce qui s'exporte

sortie contenu
Fiches participants nom, appartenance, titre, et pour chaque tour la table et le siège
Feuilles par table une table, tous les tours, les convives numérotés
Liste d'accueil tous les participants par ordre alphabétique, table de chaque tour en regard
Plan de salle vectoriel, listes comprises, un tour par page
Rapport de placement indicateurs, écart au plafond, diagnostic
Page de qualité l'en-tête chiffré, les graphiques, les tableaux

La liste d'accueil est la feuille la plus manipulée de la soirée : sans elle, retrouver une personne oblige à parcourir les feuilles de table.

Le plan ne tient pas toujours sur une page. Le logiciel calcule l'échelle qui le ferait tenir ; si la taille de caractère tombe sous le seuil mesuré de lisibilité, il découpe en pages contiguës, chacune portant un cartouche de repérage. Le choix est offert, le découpage étant proposé par défaut.

Chaque page porte en pied le nom de l'événement, sa date, le tour, et l'horodatage du placement exporté — 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.

Un plan qui n'est ni retenu ni bloqué s'exporte avec la mention BROUILLON en filigrane. Le logiciel n'interdit pas d'imprimer un brouillon ; il interdit qu'on le confonde avec le plan définitif.


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.

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, le minimum en premier, chacun avec son plafond réalisé et la mention de la population mesurée.

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 que la courbe ne dépasse jamais son plafond. Si elle le dépasse, 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.

Comparer deux placements sur le même axe. 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.

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.

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
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

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.
  • 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.

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.

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.


13. Architecture

13.1 Forme du projet

Une application Cordova, dont le code vit dans www/ et ne dépend d'aucun service.

plateforme rôle
electron la livraison : un exécutable Windows
browser le développement et les tests sous Linux
android ouvert, non requis pour la première livraison

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.

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 Cordova. 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.

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.
  • Le PDF vectoriel : la bibliothèque du § 11.1 ingère une géométrie vectorielle, 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 et pourquoi, afin que personne ne la « corrige » plus tard par le calcul.

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 ».

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 trois niveaux d'épreuve

niveau ce qu'il couvre quand
node moteur, géométrie, analyseur CSV, stockage, PDF à chaque modification, quelques secondes
navigateur ce que l'interface dessine, ce que les gestes produisent, les coordonnées avant chaque livraison
manuel une soirée complète, de la saisie à l'impression avant chaque livraison au client

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.

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.

14.7 Le déterminisme

Aucune source non reproductible — Math.random, Date.now, performance.now, crypto.getRandomValues — dans le moteur ni dans le générateur de démonstrations. Un test de l'arborescence le refuse, et échoue si son balayage ne trouve aucun fichier. L'interdit ne porte pas sur le reste de l'application : l'historique horodate ses entrées.


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.

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é ≤ nombre de groupes — puis construit un tour témoin. 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 » et tous ses compteurs à 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 paire par tour, quatre sur quatre tours, quel que soit l'algorithme. Une personne déplacée d'un groupe à l'autre fait basculer le logiciel de « minimum atteint » à « quatre paires 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.
  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é. 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.

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. 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 la plateforme electron de Cordova, qui s'appuie sur electron-builder.

Cette capacité est à vérifier dès la première semaine, sur un squelette vide, au même titre que l'ingestion du SVG par la bibliothèque PDF (§ 11.1). 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.

16.1 Les deux guides livrés

document contenu
GUIDE-WINDOWS.md un seul fichier : obtenir l'exécutable, le lancer, où vivent les fichiers d'événements, comment les sauvegarder, que faire si l'antivirus bloque, et comment signaler un problème
GUIDE-USAGE.md le parcours d'une soirée, de la liste de participants à l'impression, en une dizaine d'écrans commentés

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.


17. Ce que ce document ne tranche pas

  1. Le format de stockage des placements à l'intérieur du fichier — la forme lisible et la forme compacte n'ont pas le même coût, et le § 8.6 pose un ordre de grandeur, pas une structure.
  2. La bibliothèque PDF, et la confirmation qu'elle ingère le SVG produit.
  3. Le mécanisme exact de la commande « Poser les animateurs » : quelle table reçoit quel animateur. Un tour par numéro de table est le plus simple ; rien n'établit que c'est ce que l'opérateur attend quand les appartenances sont inégalement réparties.
  4. Le coût de la maximisation du plafond a priori sur les itinéraires admissibles, quand des réservations de tour désigné fixent une partie du parcours. L'énumération est annoncée immédiate pour une dizaine de tables ; aucune mesure n'a été faite sur 33 tables et 4 tours.
  5. Comment la page de qualité compare deux propositions dont les escaliers de plafond diffèrent, cas que produit la variante de la grande démonstration.
  6. Le seuil de zoom qui retire les listes de noms : il se fixe sur une mesure du temps par image, pas sur une intuition.
  7. Les dégagements de référence d'une salle, sans lesquels le mot « conforme » reste absent de l'interface.