[ADD] bench: a priori ceiling cost on the largest and worst events
Spec 17 point 4 asks whether the a priori ceiling is cheap enough to compute
on every check. The bench times plafondsAPriori and diagnostiquer on the
large demonstration, its variant without exceptions, and a pessimistic
event of 260 people, 33 tables of 8 and 10 rounds with every signature
distinct: about 0.075 ms, 0.04 ms and 11.5 ms. A long test guards each at
four times its reference, so losing the memoisation turns it red.
Checked: red first under that mutant; 3208 node and 71 long tests green.
--- FR ---
[ADD] banc : coût du plafond a priori, grand événement et pire cas
Le spec 17, point 4, demande si le plafond a priori se calcule assez vite
pour chaque contrôle. Le banc mesure plafondsAPriori et diagnostiquer sur
la grande démonstration, sa variante sans exception, et un événement
pessimiste de 260 personnes, 33 tables de 8 et 10 tours aux signatures
toutes distinctes : environ 0,075 ms, 0,04 ms et 11,5 ms. Une épreuve
longue garde chacun à quatre fois sa référence : perdre la mémoïsation la
rend rouge.
Vérifié : rouge d'abord sous ce mutant ; 3208 node et 71 longues vertes.
Assisted-by: Claude Opus 5.5
2026-10-09 04:53:10 -04:00
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// Le relevé du coût du plafond a priori (§ 17, point 4, § 19.10), sous node :
|
|
|
|
|
|
// pour chaque configuration, la médiane de la durée de plafondsAPriori sur
|
|
|
|
|
|
// l'instance normalisée, et celle de diagnostiquer sur la configuration, qui
|
|
|
|
|
|
// rappelle le plafond a priori dans ses sondes. L'horloge est reçue en
|
|
|
|
|
|
// paramètre : le script lancé passe performance.now, les épreuves une
|
|
|
|
|
|
// horloge factice. Le relevé ne porte que la clé, la forme et les médianes :
|
|
|
|
|
|
// ni date, ni nom d'hôte, ni chemin, ni utilisateur.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Lancé, il mesure les configurations du banc et imprime le relevé en JSON,
|
|
|
|
|
|
// les médianes arrondies à la microseconde :
|
|
|
|
|
|
//
|
|
|
|
|
|
// node scripts/banc/plafond.js [répétitions] (défaut : 21)
|
|
|
|
|
|
import { realpathSync } from 'node:fs';
|
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
|
import { configurationPessimiste } from '../../test/fixtures/banc/pessimiste.js';
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
import { configurationDe } from '../../src/application/configuration.js';
|
[ADD] bench: a priori ceiling cost on the largest and worst events
Spec 17 point 4 asks whether the a priori ceiling is cheap enough to compute
on every check. The bench times plafondsAPriori and diagnostiquer on the
large demonstration, its variant without exceptions, and a pessimistic
event of 260 people, 33 tables of 8 and 10 rounds with every signature
distinct: about 0.075 ms, 0.04 ms and 11.5 ms. A long test guards each at
four times its reference, so losing the memoisation turns it red.
Checked: red first under that mutant; 3208 node and 71 long tests green.
--- FR ---
[ADD] banc : coût du plafond a priori, grand événement et pire cas
Le spec 17, point 4, demande si le plafond a priori se calcule assez vite
pour chaque contrôle. Le banc mesure plafondsAPriori et diagnostiquer sur
la grande démonstration, sa variante sans exception, et un événement
pessimiste de 260 personnes, 33 tables de 8 et 10 tours aux signatures
toutes distinctes : environ 0,075 ms, 0,04 ms et 11,5 ms. Une épreuve
longue garde chacun à quatre fois sa référence : perdre la mémoïsation la
rend rouge.
Vérifié : rouge d'abord sous ce mutant ; 3208 node et 71 longues vertes.
Assisted-by: Claude Opus 5.5
2026-10-09 04:53:10 -04:00
|
|
|
|
import { LIVREES } from '../../src/demo/livrees.js';
|
|
|
|
|
|
import { normaliser } from '../../src/moteur/configuration.js';
|
|
|
|
|
|
import { diagnostiquer } from '../../src/moteur/diagnostic.js';
|
|
|
|
|
|
import { plafondsAPriori } from '../../src/moteur/plafond.js';
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
import { analyser } from '../../src/stockage/document.js';
|
[ADD] bench: a priori ceiling cost on the largest and worst events
Spec 17 point 4 asks whether the a priori ceiling is cheap enough to compute
on every check. The bench times plafondsAPriori and diagnostiquer on the
large demonstration, its variant without exceptions, and a pessimistic
event of 260 people, 33 tables of 8 and 10 rounds with every signature
distinct: about 0.075 ms, 0.04 ms and 11.5 ms. A long test guards each at
four times its reference, so losing the memoisation turns it red.
Checked: red first under that mutant; 3208 node and 71 long tests green.
--- FR ---
[ADD] banc : coût du plafond a priori, grand événement et pire cas
Le spec 17, point 4, demande si le plafond a priori se calcule assez vite
pour chaque contrôle. Le banc mesure plafondsAPriori et diagnostiquer sur
la grande démonstration, sa variante sans exception, et un événement
pessimiste de 260 personnes, 33 tables de 8 et 10 tours aux signatures
toutes distinctes : environ 0,075 ms, 0,04 ms et 11,5 ms. Une épreuve
longue garde chacun à quatre fois sa référence : perdre la mémoïsation la
rend rouge.
Vérifié : rouge d'abord sous ce mutant ; 3208 node et 71 longues vertes.
Assisted-by: Claude Opus 5.5
2026-10-09 04:53:10 -04:00
|
|
|
|
|
|
|
|
|
|
// Les démonstrations livrées que le § 19.10 nomme : la grande et sa variante.
|
|
|
|
|
|
const DEMONSTRATIONS_MESUREES = ['grande', 'grande-sans-exception'];
|
|
|
|
|
|
|
|
|
|
|
|
// Le nombre de répétitions du script lancé : impair, la médiane est une
|
|
|
|
|
|
// durée mesurée.
|
|
|
|
|
|
const REPETITIONS_PAR_DEFAUT = 21;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Médiane d'une liste de nombres : l'élément du milieu de la liste triée,
|
|
|
|
|
|
* ou la moyenne des deux du milieu quand elle est de longueur paire. Ne
|
|
|
|
|
|
* modifie pas la liste.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {number[]} valeurs
|
|
|
|
|
|
* @returns {number}
|
|
|
|
|
|
* @throws {RangeError} sur une liste vide
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mediane(valeurs) {
|
|
|
|
|
|
if (valeurs.length === 0) throw new RangeError('médiane d’une liste vide');
|
|
|
|
|
|
const triees = [...valeurs].sort((a, b) => a - b);
|
|
|
|
|
|
const milieu = Math.floor(triees.length / 2);
|
|
|
|
|
|
return triees.length % 2 === 1 ? triees[milieu] : (triees[milieu - 1] + triees[milieu]) / 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Les durées de repetitions appels de mesure, chacune la différence de deux
|
|
|
|
|
|
// lectures de maintenant, l'une juste avant l'appel, l'autre juste après.
|
|
|
|
|
|
function durees(mesure, maintenant, repetitions) {
|
|
|
|
|
|
const relevees = [];
|
|
|
|
|
|
for (let i = 0; i < repetitions; i += 1) {
|
|
|
|
|
|
const debut = maintenant();
|
|
|
|
|
|
mesure();
|
|
|
|
|
|
relevees.push(maintenant() - debut);
|
|
|
|
|
|
}
|
|
|
|
|
|
return relevees;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Refuse les options avant toute mesure, dans l'ordre : configurations,
|
|
|
|
|
|
// répétitions, horloge, puis chaque entrée.
|
|
|
|
|
|
function exigerOptions({ configurations, maintenant, repetitions }) {
|
|
|
|
|
|
if (!Array.isArray(configurations)) throw new TypeError('configurations : une liste est attendue');
|
|
|
|
|
|
if (configurations.length === 0) throw new RangeError('configurations : au moins une configuration');
|
|
|
|
|
|
if (!Number.isInteger(repetitions) || repetitions < 1) {
|
|
|
|
|
|
throw new RangeError(`repetitions : un entier ≥ 1 est attendu, reçu ${String(repetitions)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof maintenant !== 'function') throw new TypeError('maintenant : une fonction est attendue');
|
|
|
|
|
|
const vues = new Set();
|
|
|
|
|
|
for (const { cle } of configurations) {
|
|
|
|
|
|
if (typeof cle !== 'string' || cle === '') throw new TypeError('cle : une chaîne non vide est attendue');
|
|
|
|
|
|
if (vues.has(cle)) throw new RangeError(`cle en double : ${cle}`);
|
|
|
|
|
|
vues.add(cle);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Mesure le coût du plafond a priori sur chaque configuration, dans l'ordre
|
|
|
|
|
|
* reçu. Pour chacune : normaliser, hors mesure ; un appel de chauffe de
|
|
|
|
|
|
* plafondsAPriori et de diagnostiquer, hors mesure, sans lire l'horloge ;
|
|
|
|
|
|
* puis repetitions appels mesurés de plafondsAPriori(instance), puis
|
|
|
|
|
|
* repetitions de diagnostiquer(configuration). L'horloge se lit deux fois par
|
|
|
|
|
|
* appel mesuré, et jamais ailleurs.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object} options
|
|
|
|
|
|
* @param {Array<{cle: string, configuration: import('../../src/moteur/types.js').Configuration}>} options.configurations
|
|
|
|
|
|
* au moins une ; des clés non vides et distinctes
|
|
|
|
|
|
* @param {() => number} options.maintenant l'horloge, en millisecondes
|
|
|
|
|
|
* @param {number} options.repetitions entier ≥ 1
|
|
|
|
|
|
* @returns {Array<{cle: string, N: number, T: number, R: number, msPlafonds: number, msDiagnostic: number}>}
|
|
|
|
|
|
* N, T, R de l'instance normalisée ; les médianes des durées, en millisecondes
|
|
|
|
|
|
* @throws {TypeError} configurations qui n'est pas une liste, maintenant qui
|
|
|
|
|
|
* n'est pas une fonction, une clé vide ou qui n'est pas une chaîne
|
|
|
|
|
|
* @throws {RangeError} une liste vide, repetitions qui n'est pas un entier
|
|
|
|
|
|
* ≥ 1, une clé en double ; et ce que normaliser lève
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function mesurerPlafondAPriori({ configurations, maintenant, repetitions }) {
|
|
|
|
|
|
exigerOptions({ configurations, maintenant, repetitions });
|
|
|
|
|
|
return configurations.map(({ cle, configuration }) => {
|
|
|
|
|
|
const instance = normaliser(configuration);
|
|
|
|
|
|
plafondsAPriori(instance);
|
|
|
|
|
|
diagnostiquer(configuration);
|
|
|
|
|
|
const plafonds = durees(() => plafondsAPriori(instance), maintenant, repetitions);
|
|
|
|
|
|
const diagnostic = durees(() => diagnostiquer(configuration), maintenant, repetitions);
|
|
|
|
|
|
return {
|
|
|
|
|
|
cle,
|
|
|
|
|
|
N: instance.N,
|
|
|
|
|
|
T: instance.T,
|
|
|
|
|
|
R: instance.R,
|
|
|
|
|
|
msPlafonds: mediane(plafonds),
|
|
|
|
|
|
msDiagnostic: mediane(diagnostic),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Les configurations du banc, dans l'ordre du relevé : la grande
|
|
|
|
|
|
* démonstration et sa variante, lues dans leur fichier livré (src/demo/
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
* livrees.js) comme l'opérateur les ouvre, par configurationDe, animateurs
|
|
|
|
|
|
* compris, puis la configuration pessimiste
|
[ADD] bench: a priori ceiling cost on the largest and worst events
Spec 17 point 4 asks whether the a priori ceiling is cheap enough to compute
on every check. The bench times plafondsAPriori and diagnostiquer on the
large demonstration, its variant without exceptions, and a pessimistic
event of 260 people, 33 tables of 8 and 10 rounds with every signature
distinct: about 0.075 ms, 0.04 ms and 11.5 ms. A long test guards each at
four times its reference, so losing the memoisation turns it red.
Checked: red first under that mutant; 3208 node and 71 long tests green.
--- FR ---
[ADD] banc : coût du plafond a priori, grand événement et pire cas
Le spec 17, point 4, demande si le plafond a priori se calcule assez vite
pour chaque contrôle. Le banc mesure plafondsAPriori et diagnostiquer sur
la grande démonstration, sa variante sans exception, et un événement
pessimiste de 260 personnes, 33 tables de 8 et 10 tours aux signatures
toutes distinctes : environ 0,075 ms, 0,04 ms et 11,5 ms. Une épreuve
longue garde chacun à quatre fois sa référence : perdre la mémoïsation la
rend rouge.
Vérifié : rouge d'abord sous ce mutant ; 3208 node et 71 longues vertes.
Assisted-by: Claude Opus 5.5
2026-10-09 04:53:10 -04:00
|
|
|
|
* (test/fixtures/banc/pessimiste.js). Des objets neufs à chaque appel.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @returns {Array<{cle: string, configuration: import('../../src/moteur/types.js').Configuration}>}
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function configurationsDuBanc() {
|
|
|
|
|
|
const livrees = DEMONSTRATIONS_MESUREES.map((cle) => ({
|
|
|
|
|
|
cle,
|
[ADD] tags and goals: engine measures, CSV column, commands, bridge
Tags on people and goals on tables now reach every layer below the
screens. The engine measures rounds served, never-served people and
facilitators met, with a floor per goal mode at the diagnostic; the CSV
reads and writes an etiquettes column through the shared split and
spelling reconciliation; commands set tags, goals, their rule and the
two optional criteria, each refusal with cause and remedy; facilitators
and the stored priority order reach the engine through one bridge.
Without goals or the facilitator case, plans equal the reference prints.
Checked: node 4373, navigateur 885, node-long 113.
--- FR ---
[ADD] étiquettes et objectifs : mesures, colonne CSV, commandes, pont
Les étiquettes des personnes et les objectifs des tables atteignent toutes
les couches sous les écrans. Le moteur mesure les tours servis, les
personnes jamais servies et les animateurs rencontrés, avec un plancher
par mode au diagnostic ; le CSV lit et écrit une colonne d'étiquettes par
la découpe et la réconciliation communes ; des commandes posent étiquettes,
objectifs, règle et les deux critères facultatifs, chaque refus avec cause
et remède ; animateurs et ordre écrit passent au moteur par un seul pont.
Sans objectif ni case des animateurs, les plans égalent les empreintes.
Vérifié : node 4373, navigateur 885, node-long 113.
Assisted-by: Claude Opus 5.5
2026-10-11 15:03:13 -04:00
|
|
|
|
configuration: configurationDe(analyser(LIVREES.find((livree) => livree.cle === cle).texte).charge),
|
[ADD] bench: a priori ceiling cost on the largest and worst events
Spec 17 point 4 asks whether the a priori ceiling is cheap enough to compute
on every check. The bench times plafondsAPriori and diagnostiquer on the
large demonstration, its variant without exceptions, and a pessimistic
event of 260 people, 33 tables of 8 and 10 rounds with every signature
distinct: about 0.075 ms, 0.04 ms and 11.5 ms. A long test guards each at
four times its reference, so losing the memoisation turns it red.
Checked: red first under that mutant; 3208 node and 71 long tests green.
--- FR ---
[ADD] banc : coût du plafond a priori, grand événement et pire cas
Le spec 17, point 4, demande si le plafond a priori se calcule assez vite
pour chaque contrôle. Le banc mesure plafondsAPriori et diagnostiquer sur
la grande démonstration, sa variante sans exception, et un événement
pessimiste de 260 personnes, 33 tables de 8 et 10 tours aux signatures
toutes distinctes : environ 0,075 ms, 0,04 ms et 11,5 ms. Une épreuve
longue garde chacun à quatre fois sa référence : perdre la mémoïsation la
rend rouge.
Vérifié : rouge d'abord sous ce mutant ; 3208 node et 71 longues vertes.
Assisted-by: Claude Opus 5.5
2026-10-09 04:53:10 -04:00
|
|
|
|
}));
|
|
|
|
|
|
return [...livrees, { cle: 'pessimiste', configuration: configurationPessimiste() }];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Le nombre de répétitions lu en argument, ou null s'il n'est pas un entier
|
|
|
|
|
|
// décimal ≥ 1.
|
|
|
|
|
|
function repetitionsLues(argument) {
|
|
|
|
|
|
if (argument === undefined) return REPETITIONS_PAR_DEFAUT;
|
|
|
|
|
|
if (!/^[1-9]\d*$/.test(argument)) return null;
|
|
|
|
|
|
return Number(argument);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Lance le relevé et l'imprime ; rend le code de sortie : 0, ou 2 sur un
|
|
|
|
|
|
// argument refusé, sans rien imprimer sur la sortie standard.
|
|
|
|
|
|
function executer(argumentsRecus) {
|
|
|
|
|
|
const repetitions = repetitionsLues(argumentsRecus[0]);
|
|
|
|
|
|
if (repetitions === null || argumentsRecus.length > 1) {
|
|
|
|
|
|
process.stderr.write('usage : node scripts/banc/plafond.js [répétitions, entier ≥ 1]\n');
|
|
|
|
|
|
return 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
const releve = mesurerPlafondAPriori({
|
|
|
|
|
|
configurations: configurationsDuBanc(),
|
|
|
|
|
|
maintenant: () => performance.now(),
|
|
|
|
|
|
repetitions,
|
|
|
|
|
|
});
|
|
|
|
|
|
const arrondi = (duree) => Math.round(duree * 1000) / 1000;
|
|
|
|
|
|
const imprime = releve.map((entree) => ({
|
|
|
|
|
|
...entree,
|
|
|
|
|
|
msPlafonds: arrondi(entree.msPlafonds),
|
|
|
|
|
|
msDiagnostic: arrondi(entree.msDiagnostic),
|
|
|
|
|
|
}));
|
|
|
|
|
|
process.stdout.write(`${JSON.stringify(imprime, null, 2)}\n`);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Vrai quand ce fichier est le script que Node a lancé, et non un module
|
|
|
|
|
|
// importé.
|
|
|
|
|
|
function estLanceDirectement() {
|
|
|
|
|
|
if (process.argv[1] === undefined) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (estLanceDirectement()) process.exitCode = executer(process.argv.slice(2));
|