Files
parenthese/tools/layout-bench/run.mjs
T
IliesAllaliandClaude Fable 5.1 3be95b1a0f Banc du placement v2 : invariants, défauts sur le tracé réel, archétypes en texte, stabilité
Le banc mesure maintenant le code de production (computeBlockLayout), sans
écran, en quatre étages lus dans l'ordre et jamais additionnés :

- invariants qui doivent valoir zéro (chevauchement, couple non adjacent,
  fratrie coupée ou hors de l'ordre choisi, rangée différente de la génération
  de parenté recalculée ici, années de naissance qui redescendent) ;
- défauts comptés sur la géométrie vraiment dessinée (renvois, lignes de
  fratrie au-dessus d'un portrait étranger, croisements, lignes et foyers plus
  larges qu'un écran, proches de la référence visibles dans un écran) ;
- quatorze archétypes (ordre d'une fratrie, issue #67, branche courte, Marc et
  Nadia, petit conjoint, fratrie de douze, remariage, cousins, oncle et nièce,
  parent seul, adoption, deux familles, belle-famille, trois familles
  enchaînées) rendus en texte, témoins dans __snapshots__/archetypes, testés
  par vitest ; deux échecs connus (belle-famille, trois-belles-familles) en
  attendant la règle « le sang de la référence garde son enfant » ;
- stabilité : retirer une feuille ne devrait déplacer que son foyer.

Les modèles ELK écartés en septembre, elk-common et l'ancienne mesure sur
segments droits sont supprimés, avec la dépendance elkjs. Le générateur
d'arbres fictifs (gen-fr.mjs) entre dans le dépôt ; les arbres réels restent
dans fixtures/, ignoré par git.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-19 14:14:03 +02:00

56 lines
3.8 KiB
JavaScript

// Banc du placement, sur le code de production.
// Usage : node --import ./register.mjs run.mjs [arbre ...]
// sans argument : tous les archétypes, desc-3, asc-23 et chaque fixtures/*.json
// SRC=<dossier frontend/src> code mesuré (défaut : celui du dépôt)
// TAG=<nom> nom du rapport, out/rapport-<nom>.json (défaut : « courant »)
// ASCII=1 imprime le rendu texte des archétypes
// PERTURB=0 saute le test de stabilité (lent sur les gros arbres)
// Lecture : d'abord les invariants (doivent valoir 0), puis les défauts sur le tracé réel dans l'ordre
// renvois, fils sur portraits étrangers, croisements, câbles d'un écran, foyers larges, puis la stabilité.
// La largeur ne départage qu'une égalité parfaite. Pas de score composite.
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { performance } from 'node:perf_hooks'
import { loadTree, layout, archetypeNames } from './load.mjs'
import { checkInvariants, HARD } from './invariants.mjs'
import { computeDefects } from './defects.mjs'
import { perturbation } from './perturb.mjs'
import { renderAscii } from './ascii.mjs'
import { renderSvg } from './svg.mjs'
const OUT = fileURLToPath(new URL('./out/', import.meta.url))
mkdirSync(OUT, { recursive: true })
const tag = process.env.TAG || 'courant'
const fixturesDir = fileURLToPath(new URL('./fixtures/', import.meta.url))
const fixtures = (existsSync(fixturesDir) ? readdirSync(fixturesDir, { withFileTypes: true }) : []).filter((f) => f.isFile() && f.name.endsWith('.json')).map((f) => f.name.replace(/(-graph)?\.json$/, ''))
const names = process.argv.slice(2).length ? process.argv.slice(2) : [...archetypeNames, 'desc-3', 'asc-23', ...fixtures]
const rows = []
for (const name of names) {
const tree = loadTree(name)
const t0 = performance.now()
const { result, coupleBarMeta } = layout(tree.graph, tree.reference)
const ms = Math.round(performance.now() - t0)
const inv = checkInvariants(result, coupleBarMeta, tree.graph, tree.reference)
const def = computeDefects(result, coupleBarMeta, tree.graph, tree.reference)
const stab = process.env.PERTURB === '0' ? {} : perturbation((g, r) => layout(g, r).result, tree.graph, tree.reference)
const nodes = result.children
const largeur = Math.round(Math.max(...nodes.map((n) => n.x + n.width)) - Math.min(...nodes.map((n) => n.x)))
const { pires_foyers, ...defauts } = def
const row = { arbre: name, personnes: nodes.filter((n) => n._type === 'person').length, ms, ...inv, ...defauts, ...stab, largeur }
row.invariants = HARD.every((k) => inv[k] === 0) ? 'ok' : 'FAUX'
rows.push({ ...row, pires_foyers })
if (tree.archetype) {
const text = renderAscii(result, coupleBarMeta)
writeFileSync(`${OUT}${name}.txt`, text + '\n')
if (process.env.ASCII) console.log(`\n── ${name} ──\n${text}`)
} else {
renderSvg(result, coupleBarMeta, `${OUT}${name}.svg`, name)
}
}
const shown = ['arbre', 'personnes', 'invariants', 'rangees_fausses', 'conflits', 'fratries_coupees', 'renvois', 'renvois_sang', 'fils_sur_etranger', 'croisements', 'superpositions', 'cables_ecran', 'foyers_larges', 'proches_ecran', 'proches', 'max_bouges', 'max_deplacement_px', 'largeur', 'ms']
console.table(rows.map((r) => Object.fromEntries(shown.filter((k) => k in r).map((k) => [k, r[k]]))))
for (const r of rows) if (r.pires_foyers?.length && r.pires_foyers[0].etendue > 1440) console.log(`${r.arbre} : foyers les plus étendus`, r.pires_foyers.map((h) => `${h.enfants} enfants sur ${h.etendue} px`).join(' · '))
writeFileSync(`${OUT}rapport-${tag}.json`, JSON.stringify(rows, null, 2))
console.log(`\nrapport : out/rapport-${tag}.json · rendus texte des archétypes dans out/<slug>.txt · comparer deux rapports : node diff.mjs <tag A> <tag B>`)