[ADD] bench: frame time, readable seats and witness string thresholds
Spec 19.10 leaves three rendering thresholds to measurement: the frame time
of the plan while zooming, the smallest seat target that stays readable,
and the width of the witness string under the embedded font. Three bench
files measure them in Chromium, frame time throttled four times, and write
each measure with its own CPU factor into the record; seuils.js derives the
threshold from the samples. No value enters the registry from here.
Checked: red first; 3576 node tests green from the index; the bench
project runs the three measures.
--- FR ---
[ADD] banc : seuils du temps par image, sièges lisibles, chaîne témoin
Le § 19.10 laisse trois seuils de rendu à la mesure : le temps par image du
plan pendant un zoom, la plus petite cible de siège qui reste lisible, et
la largeur de la chaîne témoin sous la police embarquée. Trois fichiers du
banc les mesurent dans Chromium, le temps par image ralenti quatre fois, et
écrivent chaque mesure avec son propre facteur au relevé ; seuils.js tire
le seuil des échantillons. Aucune valeur n'entre au registre d'ici.
Vérifié : rouge d'abord ; 3576 node verts depuis l'index ; le projet du
banc joue les trois mesures.
Assisted-by: Claude Opus 5.5
2026-10-09 15:27:10 -04:00
|
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
|
|
// Les seuils du banc de mesure (§ 19.10, § 19.4, § 6.2), tirés de mesures
|
|
|
|
|
|
// déjà prises : le plus grand entier qui tient un prédicat, par dichotomie ;
|
[ADD] quality page: walkthrough captures, edge cases, contrast guards
The quality page is now photographed and guarded like the other
screens. The walkthrough captures each view and the seven edge cases
where a sentence replaces a chart; the transversal guards measure the
page's marks, keyboard access and tooltips, a dotted reference line
against the chart background and a run point against what its trace
covers. Proved and remaining shares differ by outline, not colour
alone; tooltips keep clear of the sticky banner; rank axes print whole
numbers. The two bench thresholds of the page are measured.
Checked: node 5096, navigateur 1137, node-long 128; captures published.
--- FR ---
[ADD] page de qualité : parcours, cas limites, gardes du contraste
La page de qualité est désormais photographiée et gardée comme les autres
écrans. Le parcours photographie chaque vue et les sept cas limites où une
phrase remplace un graphique ; les gardes transversales mesurent ses
marques, son clavier et ses infobulles, un repère pointillé contre le fond
du graphique, un point de relevé contre ce que son tracé recouvre. Prouvé
et reste se distinguent par le contour, pas par la seule couleur ; les
infobulles évitent le bandeau collant ; les rangs s'écrivent en entiers.
Les deux seuils de la page sont mesurés au banc.
Vérifié : node 5096, navigateur 1137, longues 128 ; captures publiées.
Assisted-by: Claude Opus 5.5
2026-10-12 16:38:04 -04:00
|
|
|
|
// le nom de référence d'une largeur de troncature, fait du glyphe le plus
|
|
|
|
|
|
// large (§ 14.1) ; le plus petit zoom au-delà duquel une courbe de temps par
|
|
|
|
|
|
// image tient son critère ; la tolérance de la chaîne témoin. Module pur,
|
|
|
|
|
|
// sans import ni horloge : il se charge sous node, où ses épreuves le
|
|
|
|
|
|
// jouent, comme dans la page du banc, où les *.banc.js lui passent leurs
|
|
|
|
|
|
// mesures.
|
[ADD] bench: frame time, readable seats and witness string thresholds
Spec 19.10 leaves three rendering thresholds to measurement: the frame time
of the plan while zooming, the smallest seat target that stays readable,
and the width of the witness string under the embedded font. Three bench
files measure them in Chromium, frame time throttled four times, and write
each measure with its own CPU factor into the record; seuils.js derives the
threshold from the samples. No value enters the registry from here.
Checked: red first; 3576 node tests green from the index; the bench
project runs the three measures.
--- FR ---
[ADD] banc : seuils du temps par image, sièges lisibles, chaîne témoin
Le § 19.10 laisse trois seuils de rendu à la mesure : le temps par image du
plan pendant un zoom, la plus petite cible de siège qui reste lisible, et
la largeur de la chaîne témoin sous la police embarquée. Trois fichiers du
banc les mesurent dans Chromium, le temps par image ralenti quatre fois, et
écrivent chaque mesure avec son propre facteur au relevé ; seuils.js tire
le seuil des échantillons. Aucune valeur n'entre au registre d'ici.
Vérifié : rouge d'abord ; 3576 node verts depuis l'index ; le projet du
banc joue les trois mesures.
Assisted-by: Claude Opus 5.5
2026-10-09 15:27:10 -04:00
|
|
|
|
|
|
|
|
|
|
/** Le temps par image qu'un glissement de la grande démonstration doit tenir,
|
|
|
|
|
|
* au p95, sur la machine de référence (Q13 b) : le choix de l'utilisateur.
|
|
|
|
|
|
* Il juge une garde du banc, pas une constante : il ne vit pas au registre. */
|
|
|
|
|
|
export const CRITERE_TEMPS_IMAGE_MS = 33;
|
|
|
|
|
|
|
|
|
|
|
|
// Le nombre de mesures sous lequel une gigue ne se lit pas (§ 19.4).
|
|
|
|
|
|
const MESURES_MIN = 10;
|
|
|
|
|
|
|
|
|
|
|
|
const estEntier = (valeur) => Number.isSafeInteger(valeur);
|
|
|
|
|
|
|
|
|
|
|
|
// La réponse du prédicat en n, qui doit être un booléen.
|
|
|
|
|
|
async function lire(predicat, n) {
|
|
|
|
|
|
const reponse = await predicat(n);
|
|
|
|
|
|
if (typeof reponse !== 'boolean') throw new TypeError(`plusGrandTenant : booléen attendu du prédicat en ${n}, reçu ${String(reponse)}`);
|
|
|
|
|
|
return reponse;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Le plus grand entier n de [min, max] où predicat(n) tient, pour un
|
|
|
|
|
|
* prédicat décroissant — vrai jusqu'à un seuil, faux au-delà. Le prédicat se
|
|
|
|
|
|
* lit aux deux bornes, puis par dichotomie entre le dernier n qui tient et le
|
|
|
|
|
|
* premier qui ne tient pas ; enfin le résultat r et ses deux voisins se
|
|
|
|
|
|
* relisent : r − 1 et r doivent tenir, r + 1 non, quand ils sont dans
|
|
|
|
|
|
* l'intervalle. Une relecture qui dément la dichotomie — un trou sous le
|
|
|
|
|
|
* seuil, une mesure qui change d'avis — lève en nommant l'entier fautif.
|
|
|
|
|
|
* Une monotonie rompue loin du résultat ne se voit pas : la dichotomie ne
|
|
|
|
|
|
* lit qu'une dizaine de points.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {(n: number) => boolean|Promise<boolean>} predicat
|
|
|
|
|
|
* @param {number} min entier
|
|
|
|
|
|
* @param {number} max entier ≥ min
|
|
|
|
|
|
* @returns {Promise<number|null>} max quand il tient ; null quand ni min
|
|
|
|
|
|
* ni max ne tiennent
|
|
|
|
|
|
* @throws {TypeError} un prédicat qui n'est pas une fonction ou ne rend pas
|
|
|
|
|
|
* un booléen
|
|
|
|
|
|
* @throws {RangeError} des bornes qui ne sont pas des entiers ordonnés,
|
|
|
|
|
|
* avant tout appel ; un prédicat faux en min et vrai en max ; une
|
|
|
|
|
|
* relecture qui dément la dichotomie
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function plusGrandTenant(predicat, min, max) {
|
|
|
|
|
|
if (typeof predicat !== 'function') throw new TypeError('plusGrandTenant : un prédicat est attendu');
|
|
|
|
|
|
if (!estEntier(min) || !estEntier(max) || min > max) {
|
|
|
|
|
|
throw new RangeError(`plusGrandTenant : bornes entières min ≤ max attendues, reçu ${String(min)} et ${String(max)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const tientMin = await lire(predicat, min);
|
|
|
|
|
|
const tientMax = min === max ? tientMin : await lire(predicat, max);
|
|
|
|
|
|
if (!tientMin && tientMax) throw new RangeError(`plusGrandTenant : prédicat non monotone, faux en ${min} et vrai en ${max}`);
|
|
|
|
|
|
if (!tientMin) return null;
|
|
|
|
|
|
let resultat = max;
|
|
|
|
|
|
if (!tientMax) {
|
|
|
|
|
|
// Invariant : bas tient, haut ne tient pas.
|
|
|
|
|
|
let bas = min;
|
|
|
|
|
|
let haut = max;
|
|
|
|
|
|
while (haut - bas > 1) {
|
|
|
|
|
|
const milieu = bas + Math.floor((haut - bas) / 2);
|
|
|
|
|
|
if (await lire(predicat, milieu)) bas = milieu;
|
|
|
|
|
|
else haut = milieu;
|
|
|
|
|
|
}
|
|
|
|
|
|
resultat = bas;
|
|
|
|
|
|
}
|
|
|
|
|
|
const attendus = [
|
|
|
|
|
|
[resultat - 1, true],
|
|
|
|
|
|
[resultat, true],
|
|
|
|
|
|
[resultat + 1, false],
|
|
|
|
|
|
];
|
|
|
|
|
|
for (const [n, attendu] of attendus) {
|
|
|
|
|
|
if (n < min || n > max) continue;
|
|
|
|
|
|
if ((await lire(predicat, n)) !== attendu) {
|
|
|
|
|
|
throw new RangeError(`plusGrandTenant : prédicat non monotone, ${attendu ? 'faux' : 'vrai'} en ${n} à la relecture, seuil lu ${resultat}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return resultat;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const estPositifFini = (valeur) => typeof valeur === 'number' && Number.isFinite(valeur) && valeur > 0;
|
|
|
|
|
|
const estPositifOuNul = (valeur) => typeof valeur === 'number' && Number.isFinite(valeur) && valeur >= 0;
|
|
|
|
|
|
|
[ADD] quality page: walkthrough captures, edge cases, contrast guards
The quality page is now photographed and guarded like the other
screens. The walkthrough captures each view and the seven edge cases
where a sentence replaces a chart; the transversal guards measure the
page's marks, keyboard access and tooltips, a dotted reference line
against the chart background and a run point against what its trace
covers. Proved and remaining shares differ by outline, not colour
alone; tooltips keep clear of the sticky banner; rank axes print whole
numbers. The two bench thresholds of the page are measured.
Checked: node 5096, navigateur 1137, node-long 128; captures published.
--- FR ---
[ADD] page de qualité : parcours, cas limites, gardes du contraste
La page de qualité est désormais photographiée et gardée comme les autres
écrans. Le parcours photographie chaque vue et les sept cas limites où une
phrase remplace un graphique ; les gardes transversales mesurent ses
marques, son clavier et ses infobulles, un repère pointillé contre le fond
du graphique, un point de relevé contre ce que son tracé recouvre. Prouvé
et reste se distinguent par le contour, pas par la seule couleur ; les
infobulles évitent le bandeau collant ; les rangs s'écrivent en entiers.
Les deux seuils de la page sont mesurés au banc.
Vérifié : node 5096, navigateur 1137, longues 128 ; captures publiées.
Assisted-by: Claude Opus 5.5
2026-10-12 16:38:04 -04:00
|
|
|
|
// La largeur d'un texte par le mesureur reçu, qui doit être finie > 0 : un
|
|
|
|
|
|
// texte qui n'est pas rendu mesure 0, et ne mesure alors rien.
|
|
|
|
|
|
function largeurDe(mesurer, texte) {
|
|
|
|
|
|
const largeur = mesurer(texte);
|
|
|
|
|
|
if (!estPositifFini(largeur)) throw new RangeError(`nomDeReference : largeur finie > 0 attendue pour « ${texte} », reçu ${String(largeur)}`);
|
|
|
|
|
|
return largeur;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Le nom de référence d'une largeur de troncature (§ 14.1) : le glyphe le
|
|
|
|
|
|
* plus large de l'alphabet, répété longueur fois. Chaque glyphe se mesure
|
|
|
|
|
|
* une fois, dans l'ordre de l'alphabet, parcouru par points de code ; à
|
|
|
|
|
|
* égalité, le premier l'emporte. Le nom composé se mesure ensuite entier,
|
|
|
|
|
|
* et non comme longueur fois son glyphe : la mesure est celle du texte
|
|
|
|
|
|
* rendu. valeur est sa largeur arrondie au pixel supérieur, celle que le
|
|
|
|
|
|
* banc propose au registre.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {string} alphabet au moins un glyphe
|
|
|
|
|
|
* @param {number} longueur entier ≥ 1, le nombre de glyphes du nom
|
|
|
|
|
|
* @param {(texte: string) => number} mesurer largeur rendue d'un texte, px
|
|
|
|
|
|
* @returns {{glyphe: string, largeurGlyphe: number, nom: string, largeur: number, valeur: number}}
|
|
|
|
|
|
* @throws {TypeError} un alphabet qui n'est pas un texte, un mesureur qui
|
|
|
|
|
|
* n'est pas une fonction
|
|
|
|
|
|
* @throws {RangeError} un alphabet vide, une longueur hors contrat, avant
|
|
|
|
|
|
* toute mesure ; une largeur mesurée qui n'est pas finie > 0, en nommant
|
|
|
|
|
|
* son texte
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function nomDeReference(alphabet, longueur, mesurer) {
|
|
|
|
|
|
if (typeof alphabet !== 'string') throw new TypeError('nomDeReference : un alphabet en texte est attendu');
|
|
|
|
|
|
if (typeof mesurer !== 'function') throw new TypeError('nomDeReference : un mesureur est attendu');
|
|
|
|
|
|
if (alphabet.length === 0) throw new RangeError('nomDeReference : alphabet vide');
|
|
|
|
|
|
if (!Number.isSafeInteger(longueur) || longueur < 1) {
|
|
|
|
|
|
throw new RangeError(`nomDeReference : longueur entière ≥ 1 attendue, reçu ${String(longueur)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
let glyphe = null;
|
|
|
|
|
|
let largeurGlyphe = 0;
|
|
|
|
|
|
for (const candidat of alphabet) {
|
|
|
|
|
|
const largeur = largeurDe(mesurer, candidat);
|
|
|
|
|
|
if (largeur > largeurGlyphe) {
|
|
|
|
|
|
glyphe = candidat;
|
|
|
|
|
|
largeurGlyphe = largeur;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const nom = glyphe.repeat(longueur);
|
|
|
|
|
|
const largeur = largeurDe(mesurer, nom);
|
|
|
|
|
|
return { glyphe, largeurGlyphe, nom, largeur, valeur: Math.ceil(largeur) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
[ADD] bench: frame time, readable seats and witness string thresholds
Spec 19.10 leaves three rendering thresholds to measurement: the frame time
of the plan while zooming, the smallest seat target that stays readable,
and the width of the witness string under the embedded font. Three bench
files measure them in Chromium, frame time throttled four times, and write
each measure with its own CPU factor into the record; seuils.js derives the
threshold from the samples. No value enters the registry from here.
Checked: red first; 3576 node tests green from the index; the bench
project runs the three measures.
--- FR ---
[ADD] banc : seuils du temps par image, sièges lisibles, chaîne témoin
Le § 19.10 laisse trois seuils de rendu à la mesure : le temps par image du
plan pendant un zoom, la plus petite cible de siège qui reste lisible, et
la largeur de la chaîne témoin sous la police embarquée. Trois fichiers du
banc les mesurent dans Chromium, le temps par image ralenti quatre fois, et
écrivent chaque mesure avec son propre facteur au relevé ; seuils.js tire
le seuil des échantillons. Aucune valeur n'entre au registre d'ici.
Vérifié : rouge d'abord ; 3576 node verts depuis l'index ; le projet du
banc joue les trois mesures.
Assisted-by: Claude Opus 5.5
2026-10-09 15:27:10 -04:00
|
|
|
|
/**
|
|
|
|
|
|
* Le plus petit k de la courbe au-delà duquel tout k de la courbe tient le
|
|
|
|
|
|
* critère : son p95 vaut au plus critereMs. Les points se rangent par k
|
|
|
|
|
|
* croissant ; le seuil est le point qui suit le dernier échec, ou le premier
|
|
|
|
|
|
* point quand aucun n'échoue. Une courbe qui échoue à son plus grand k n'a
|
|
|
|
|
|
* pas de seuil.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Array<{k: number, p95Ms: number}>} courbe k en px/cm, fini > 0 et
|
|
|
|
|
|
* sans doublon ; p95Ms fini ≥ 0. La courbe reçue n'est pas modifiée ; ses
|
|
|
|
|
|
* autres champs sont ignorés.
|
|
|
|
|
|
* @param {number} critereMs fini > 0
|
|
|
|
|
|
* @returns {{k: number|null, tenuPartout: boolean, jamaisTenu: boolean}}
|
|
|
|
|
|
* @throws {TypeError} sur autre chose qu'une liste
|
|
|
|
|
|
* @throws {RangeError} une courbe vide — elle ne prouve rien —, un point
|
|
|
|
|
|
* malformé, un k en double, un critère qui n'est pas un nombre fini > 0
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function seuilDepuisCourbe(courbe, critereMs) {
|
|
|
|
|
|
if (!Array.isArray(courbe)) throw new TypeError('seuilDepuisCourbe : une courbe est attendue');
|
|
|
|
|
|
if (courbe.length === 0) throw new RangeError('seuilDepuisCourbe : courbe vide');
|
|
|
|
|
|
if (!estPositifFini(critereMs)) throw new RangeError(`seuilDepuisCourbe : critère fini > 0 attendu, reçu ${String(critereMs)}`);
|
|
|
|
|
|
for (const point of courbe) {
|
|
|
|
|
|
if (!estPositifFini(point?.k) || !estPositifOuNul(point?.p95Ms)) {
|
|
|
|
|
|
throw new RangeError(`seuilDepuisCourbe : point refusé, k ${String(point?.k)} et p95 ${String(point?.p95Ms)}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const points = [...courbe].sort((a, b) => a.k - b.k);
|
|
|
|
|
|
for (let i = 1; i < points.length; i += 1) {
|
|
|
|
|
|
if (points[i].k === points[i - 1].k) throw new RangeError(`seuilDepuisCourbe : k ${points[i].k} en double`);
|
|
|
|
|
|
}
|
|
|
|
|
|
const tient = points.map(({ p95Ms }) => p95Ms <= critereMs);
|
|
|
|
|
|
const dernierEchec = tient.lastIndexOf(false);
|
|
|
|
|
|
return {
|
|
|
|
|
|
k: dernierEchec === points.length - 1 ? null : points[dernierEchec + 1].k,
|
|
|
|
|
|
tenuPartout: dernierEchec === -1,
|
|
|
|
|
|
jamaisTenu: !tient.includes(true),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// La médiane d'une liste non vide, la moyenne des deux du milieu pour une
|
|
|
|
|
|
// longueur paire.
|
|
|
|
|
|
function mediane(valeurs) {
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Une valeur > 0 arrondie par excès à deux chiffres significatifs : le
|
|
|
|
|
|
// registre l'écrit telle quelle, et elle ne passe jamais sous la valeur
|
|
|
|
|
|
// reçue. toPrecision ôte le bruit du flottant que le produit laisse.
|
|
|
|
|
|
function parExces(valeur) {
|
|
|
|
|
|
const pas = 10 ** (Math.floor(Math.log10(valeur)) - 1);
|
|
|
|
|
|
let crans = Math.ceil(valeur / pas - 1e-9);
|
|
|
|
|
|
if (Number((crans * pas).toPrecision(2)) < valeur) crans += 1;
|
|
|
|
|
|
return Number((crans * pas).toPrecision(2));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Les mesures d'une série : au moins MESURES_MIN longueurs finies > 0.
|
|
|
|
|
|
function exigerMesures(nom, mesures) {
|
|
|
|
|
|
if (!Array.isArray(mesures) || mesures.length < MESURES_MIN || !mesures.every(estPositifFini)) {
|
|
|
|
|
|
throw new RangeError(`toleranceTemoin : ${nom}, au moins ${MESURES_MIN} longueurs finies > 0 attendues`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* La tolérance relative de la chaîne témoin (§ 19.4) : le plus grand du
|
|
|
|
|
|
* quart de l'écart relatif qu'introduit le repli et de trois fois la gigue
|
|
|
|
|
|
* de la police embarquée, arrondi par excès à deux chiffres significatifs.
|
|
|
|
|
|
* Chaque écart se rapporte à la longueur attendue, celle que donnent les
|
|
|
|
|
|
* avances de la police :
|
|
|
|
|
|
* - ecartRepli : |médiane(repliPx) − attenduePx| / attenduePx ;
|
|
|
|
|
|
* - gigue : (max − min) des mesures de la police embarquée / attenduePx ;
|
|
|
|
|
|
* - biais : |médiane(embarqueePx) − attenduePx| / attenduePx.
|
|
|
|
|
|
* La tolérance se refuse — valeur null, refus qui dit pourquoi — quand elle
|
|
|
|
|
|
* n'écarte pas le repli (valeur ≥ ecartRepli) ou qu'elle rejetterait la
|
|
|
|
|
|
* police embarquée elle-même (biais > valeur) : la mesure ne voit alors pas
|
|
|
|
|
|
* ce qu'elle doit voir.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {{attenduePx: number, embarqueePx: number[], repliPx: number[]}} mesures
|
|
|
|
|
|
* au moins dix longueurs rendues de chaque police, en px
|
|
|
|
|
|
* @returns {{valeur: number|null, ecartRepli: number, gigue: number, biais: number, refus: string|null}}
|
|
|
|
|
|
* @throws {RangeError} une longueur attendue qui n'est pas finie > 0, moins
|
|
|
|
|
|
* de dix mesures, une mesure qui n'est pas finie > 0
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function toleranceTemoin({ attenduePx, embarqueePx, repliPx }) {
|
|
|
|
|
|
if (!estPositifFini(attenduePx)) throw new RangeError(`toleranceTemoin : longueur attendue finie > 0, reçu ${String(attenduePx)}`);
|
|
|
|
|
|
exigerMesures('police embarquée', embarqueePx);
|
|
|
|
|
|
exigerMesures('repli', repliPx);
|
|
|
|
|
|
const relatif = (ecart) => Number((ecart / attenduePx).toPrecision(12));
|
|
|
|
|
|
const ecartRepli = relatif(Math.abs(mediane(repliPx) - attenduePx));
|
|
|
|
|
|
const gigue = relatif(Math.max(...embarqueePx) - Math.min(...embarqueePx));
|
|
|
|
|
|
const biais = relatif(Math.abs(mediane(embarqueePx) - attenduePx));
|
|
|
|
|
|
const brute = Math.max(ecartRepli / 4, 3 * gigue);
|
|
|
|
|
|
const mesure = { ecartRepli, gigue, biais };
|
|
|
|
|
|
if (brute === 0 || parExces(brute) >= ecartRepli) {
|
|
|
|
|
|
return { valeur: null, ...mesure, refus: 'la tolérance n’écarte pas le repli : le témoin ne distingue pas les deux polices' };
|
|
|
|
|
|
}
|
|
|
|
|
|
const valeur = parExces(brute);
|
|
|
|
|
|
if (biais > valeur) {
|
|
|
|
|
|
return { valeur: null, ...mesure, refus: 'la police embarquée s’écarte de ses avances au-delà de la tolérance : ce n’est pas elle qui est rendue' };
|
|
|
|
|
|
}
|
|
|
|
|
|
return { valeur, ...mesure, refus: null };
|
|
|
|
|
|
}
|