{"id":4058,"date":"2026-09-14T17:08:01","date_gmt":"2026-09-14T17:08:01","guid":{"rendered":"https:\/\/www.chess.lumn.eu\/wordpress\/?p=4058"},"modified":"2026-09-14T17:11:38","modified_gmt":"2026-09-14T17:11:38","slug":"calculateur-de-tournoi-copy","status":"publish","type":"post","link":"https:\/\/www.chess.lumn.eu\/wordpress\/2026\/09\/14\/calculateur-de-tournoi-copy\/","title":{"rendered":"Calculateur de tournoi &#8211; Copy"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"4058\" class=\"elementor elementor-4058\">\n\t\t\t\t\t\t<div class=\"elementor-inner\">\n\t\t\t\t<div class=\"elementor-section-wrap\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-6804c99 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"6804c99\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t\t\t<div class=\"elementor-row\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-fae1cb1\" data-id=\"fae1cb1\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-column-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-472af1b elementor-widget elementor-widget-heading\" data-id=\"472af1b\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<h2 class=\"elementor-heading-title elementor-size-default\">Calculateur de tournoi<\/h2>\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-929f9d9 elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"929f9d9\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t\t\t<div class=\"elementor-row\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-3d3453b\" data-id=\"3d3453b\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-column-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t<div class=\"elementor-widget-wrap\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-5b94332 elementor-widget elementor-widget-html\" data-id=\"5b94332\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<link href=\"https:\/\/fonts.googleapis.com\/css2?family=DM+Serif+Display&family=DM+Sans:wght@300;400;700&family=JetBrains+Mono:wght@400;700&display=swap\" rel=\"stylesheet\">\n<style>\n.lumn{--crf:#f7f4ee;--ink:#23201c;--soft:#5b554c;--rule:#e0d9cc;--red:#c0392b;--gold:#d4ac0d;--tan:#b8a878;--blue:#2b6ca3;\n background:var(--crf);color:var(--ink);font-family:\"DM Sans\",system-ui,Arial,sans-serif;line-height:1.55;\n max-width:980px;margin:0 auto;padding:24px 20px;border-radius:6px;}\n.lumn *{box-sizing:border-box;}\n.lumn .lh{font-family:\"JetBrains Mono\",ui-monospace,monospace;font-size:.7rem;font-weight:700;letter-spacing:.2em;text-transform:uppercase;color:var(--red);margin-bottom:8px;}\n.lumn h2.lt{font-family:\"DM Serif Display\",Georgia,serif;font-size:1.9rem;font-weight:400;margin:0 0 6px;}\n.lumn .lp{color:var(--soft);font-size:1rem;margin:0 0 16px;max-width:64ch;}\n.lumn table{width:100%;border-collapse:collapse;font-size:.9rem;}\n.lumn th{text-align:left;font-family:\"JetBrains Mono\",monospace;font-size:.66rem;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--soft);padding:8px 9px;border-bottom:2px solid var(--ink);}\n.lumn td{padding:7px 9px;border-bottom:1px solid var(--rule);}\n.lumn td.n,.lumn th.n{text-align:right;font-family:\"JetBrains Mono\",monospace;}\n.lumn .rk{color:var(--soft);font-family:\"JetBrains Mono\",monospace;font-size:.78rem;}\n.lumn .elo{color:var(--red);font-weight:700;font-family:\"JetBrains Mono\",monospace;}\n.lumn .hw{font-family:\"JetBrains Mono\",monospace;font-size:.72rem;color:var(--soft);}\n.lumn .star{color:var(--gold);}\n.lumn a.en{color:var(--red);text-decoration:none;border-bottom:1px dotted var(--red);}\n.lumn a.en:hover{background:rgba(192,57,43,.08);}\n.lumn .froz{color:var(--soft);font-style:italic;}\n.lumn .hint{font-family:\"JetBrains Mono\",monospace;font-size:.7rem;color:var(--soft);margin-top:10px;}\n\/* --- moteurs sous adaptateur Python (ponts eng2uci \/ shredder2uci) --- *\/\n.lumn a.en.py{color:var(--blue);border-bottom-color:var(--blue);}\n.lumn a.en.py:hover{background:rgba(43,108,163,.10);}\n.lumn .elo.py,.lumn .py{color:var(--blue);}\n.lumn .froz.py{color:var(--blue);font-style:italic;}\n.lumn .pytag{display:inline-block;font-family:\"JetBrains Mono\",monospace;font-size:.6rem;font-weight:700;\n letter-spacing:.08em;text-transform:uppercase;color:#fff;background:var(--blue);border-radius:3px;\n padding:1px 5px;margin-left:7px;vertical-align:1px;}\n.lumn .card{scroll-margin-top:90px;border-bottom:1px solid var(--rule);padding:6px 10px 12px;margin-bottom:10px;}\n.lumn .card:target{background:rgba(212,172,13,.10);border-radius:5px;}\n.lumn .card h3{font-family:\"DM Serif Display\",Georgia,serif;font-size:1.4rem;font-weight:400;margin:6px 0 2px;}\n.lumn .card h3.py{color:var(--blue);}\n.lumn .card .mm{font-family:\"JetBrains Mono\",monospace;font-size:.74rem;color:var(--soft);margin:0 0 12px;}\n.lumn .elobox{background:#fdf3c0;border-left:5px solid var(--gold);border-radius:3px;padding:10px 16px;margin:0 0 12px;display:flex;gap:14px;align-items:baseline;flex-wrap:wrap;}\n.lumn .elobox .l{font-family:\"JetBrains Mono\",monospace;font-size:.64rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:#8a6d00;}\n.lumn .elobox .v{font-family:\"DM Serif Display\",Georgia,serif;font-size:1.8rem;color:#6b5500;line-height:1;}\n.lumn .elobox .s{font-family:\"JetBrains Mono\",monospace;font-size:.84rem;color:#8a6d00;}\n.lumn .elobox.py{background:#e8f1f8;border-left-color:var(--blue);}\n.lumn .elobox.py .l,.lumn .elobox.py .s{color:#1d557f;}\n.lumn .elobox.py .v{color:#17456a;}\n.lumn .note{background:#f1eadb;border-left:5px solid var(--tan);border-radius:3px;padding:9px 15px;margin:10px 0 0;font-size:.87rem;color:#5a4f38;}\n.lumn .back{font-family:\"JetBrains Mono\",monospace;font-size:.72rem;margin-top:6px;}\n.lumn .back a{color:var(--soft);text-decoration:none;} .lumn .back a:hover{color:var(--red);}\n@media(max-width:640px){.lumn{padding:16px 10px;}.lumn h2.lt{font-size:1.5rem;}.lumn table{font-size:.78rem;}.lumn th,.lumn td{padding:5px 5px;}.lumn .hw,.lumn th.hwh{display:none;}}\n<\/style>\n<style>\n\/* --- complements propres a l'outil, memes variables que le fragment .lumn --- *\/\n.lumn .depot{border:2px dashed var(--rule);border-radius:8px;padding:26px 20px;text-align:center;\n background:#fbf9f4;cursor:pointer;transition:border-color .15s,background .15s;margin-bottom:14px;}\n.lumn .depot.on{border-color:var(--red);background:#f6efe6;}\n.lumn .depot .g{font-family:\"DM Serif Display\",Georgia,serif;font-size:1.25rem;}\n.lumn .depot p{margin:.25em 0;color:var(--soft);font-size:.9rem;}\n.lumn .barre{display:flex;flex-wrap:wrap;gap:12px 20px;align-items:center;background:#efe9dd;\n border-radius:6px;padding:12px 16px;margin:12px 0;font-size:.86rem;}\n.lumn .barre label{display:flex;align-items:center;gap:6px;cursor:pointer;}\n.lumn .barre input[type=text],.lumn .barre input[type=number]{font-family:\"JetBrains Mono\",monospace;\n font-size:.82rem;border:1px solid var(--rule);border-radius:4px;padding:3px 7px;}\n.lumn .etat{font-family:\"JetBrains Mono\",monospace;font-size:.78rem;color:var(--soft);margin:8px 0 14px;}\n.lumn textarea.anc{width:100%;min-height:110px;font-family:\"JetBrains Mono\",monospace;font-size:.78rem;\n border:1px solid var(--rule);border-radius:5px;padding:9px;background:#fbf9f4;color:var(--ink);}\n.lumn details.reg summary{cursor:pointer;font-family:\"JetBrains Mono\",monospace;font-size:.7rem;\n letter-spacing:.14em;text-transform:uppercase;color:var(--tan);margin:14px 0 6px;font-weight:700;}\n.lumn button.act{font-family:\"DM Sans\",sans-serif;font-weight:700;font-size:.88rem;background:var(--red);\n color:#fff;border:none;padding:8px 18px;border-radius:5px;cursor:pointer;margin:4px 6px 4px 0;}\n.lumn button.act:hover{background:#a5301f;}\n.lumn button.act.sec{background:#7a746a;}\n.lumn .vide{text-align:center;color:var(--soft);font-style:italic;padding:34px 0;}\n.lumn .fichiers{font-family:\"JetBrains Mono\",monospace;font-size:.74rem;color:var(--soft);}\n.lumn .fichiers span{display:inline-block;background:#efe9dd;border-radius:3px;padding:2px 8px;margin:2px 4px 2px 0;}\n<\/style>\n<div class=\"lumn\" id=\"lumn-outil\">\n  <div class=\"lh\">chess.lumn.eu \u00b7 outil<\/div>\n  <h2 class=\"lt\">Ordostat<\/h2>\n  <p class=\"lp\">Chargez un ou plusieurs PGN : la page calcule les cotes par ajustement global\n    (mod\u00e8le logistique, nulles \u00e0 un demi-point, avantage du trait ajust\u00e9), puis produit le\n    <strong>classement<\/strong> et les <strong>d\u00e9briefs<\/strong> adversaire par adversaire.\n    Tout se passe dans votre navigateur.<\/p>\n\n  <div class=\"depot\" id=\"depot\">\n    <p class=\"g\">D\u00e9posez vos PGN ici<\/p>\n    <p>ou cliquez pour les choisir \u00b7 plusieurs fichiers accept\u00e9s<\/p>\n    <input type=\"file\" id=\"fichier\" accept=\".pgn,.txt\" multiple hidden>\n  <\/div>\n  <p class=\"fichiers\" id=\"fichiers\"><\/p>\n\n  <div class=\"barre\">\n    <label><input type=\"checkbox\" id=\"opt-incid\" checked> \u00e9carter les parties incident\u00e9es<\/label>\n    <label><input type=\"checkbox\" id=\"opt-dedup\" checked> \u00e9carter les doublons<\/label>\n    <label><input type=\"checkbox\" id=\"opt-white\" checked> ajuster l'avantage du trait<\/label>\n    <label>parties min. <input type=\"number\" id=\"minj\" value=\"22\" min=\"1\" style=\"width:62px\"><\/label>\n    <label>mat\u00e9riel <input type=\"text\" id=\"hw\" value=\"AMD Ryzen 9 7950X3D 100%\" style=\"width:210px\"><\/label>\n  <\/div>\n\n  <details class=\"reg\">\n    <summary>Ancres Elo \u2014 moteurs de r\u00e9f\u00e9rence<\/summary>\n    <p class=\"lp\" style=\"font-size:.88rem;\">Un moteur par ligne, au format <code>\"Nom\", cote<\/code> \u2014 le format\n      d'<code>anchors.csv<\/code>. Ces moteurs gardent exactement la cote indiqu\u00e9e ; tous les autres sont\n      ajust\u00e9s autour d'eux. Deux ancres au minimum.<\/p>\n    <textarea class=\"anc\" id=\"ancres\" spellcheck=\"false\"><\/textarea>\n  <\/details>\n\n  <p style=\"margin-top:14px;\">\n    <button class=\"act\" id=\"go\">Calculer<\/button>\n    <button class=\"act sec\" id=\"copier\">Copier le fragment HTML<\/button>\n    <button class=\"act sec\" id=\"telecharger\">T\u00e9l\u00e9charger<\/button>\n  <\/p>\n  <p class=\"etat\" id=\"etat\"><\/p>\n<\/div>\n\n<div id=\"sortie\"><div class=\"lumn\"><p class=\"vide\">Aucun PGN charg\u00e9.<\/p><\/div><\/div>\n<script>\n(function(){\n\"use strict\";\n\n\/\/ ================================================================= constantes\nconst ANCRES_DEFAUT = `\"Deep Shredder 13 x64 8CPU\", 3394\n\"Glaurung 2.2 JA 8CPU\", 2990\n\"SOS 5 for Arena\", 2675`;\nconst TERM_ECARTER = ['crash','timeout','stalled connection','disconnected','terminated',\n                      'illegal move','unterminated','no result','abandoned'];\nconst PY_RE  = \/\\bUCI\\s*py\\b\/i;\nconst CPU_RE = \/\\s*\\b(\\d+)\\s*CPU\\b\\s*\/i;\n\n\/\/ ================================================================= utilitaires\nconst $ = s => document.querySelector(s);\nconst esc = s => String(s).replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;');\nconst estPy = n => PY_RE.test(n) || \/\\s+py$\/i.test(String(n).trim());\nfunction slug(n){\n  const s = n.normalize('NFKD').replace(\/[\\u0300-\\u036f]\/g,'')\n             .replace(\/[^A-Za-z0-9]+\/g,'-').replace(\/^-+|-+$\/g,'').toLowerCase();\n  return 'eng-' + (s || 'x');\n}\nfunction disp(n){                       \/\/ \"Deep Fritz 8 UCI py 8CPU\" -> \"... \u2014 8 Threads\"\n  const m = CPU_RE.exec(n);\n  const k = m ? parseInt(m[1],10) : 1;\n  const base = n.replace(CPU_RE,' ').replace(\/\\s{2,}\/g,' ').trim();\n  return base + ' \u2014 ' + (k > 1 ? k + ' Threads' : '1 Thread');\n}\nconst sigma = (p,n) => n ? 400*Math.sqrt(p*(1-p)\/n) : 0;\nfunction perfLocale(coteAdv, p){\n  if (coteAdv === undefined || coteAdv === null) return null;\n  if (p >= 1) return coteAdv + 800;\n  if (p <= 0) return coteAdv - 800;\n  return coteAdv + 400*Math.log10(p\/(1-p));\n}\nfunction celluleNom(n, href){\n  const cls = estPy(n) ? 'en py' : 'en';\n  const t = esc(disp(n));\n  return href ? `<a class=\"${cls}\" href=\"${href}\">${t}<\/a>`\n              : `<span class=\"${estPy(n)?'py':''}\">${t}<\/span>`;\n}\n\n\/\/ ================================================================= lecture PGN\nfunction lirePGN(txt, opt, st, vus){\n  const out = [];\n  for (const b of txt.replace(\/\\r\\n\/g,'\\n').split(\/(?=\\[Event\\s*\")\/)) {\n    if (!\/\\[Event\\s*\"\/.test(b)) continue;\n    const tag = n => { const m = b.match(new RegExp('\\\\[' + n + '\\\\s+\"([^\"]*)\"\\\\]')); return m ? m[1] : ''; };\n    const w = tag('White'), n = tag('Black'), r = tag('Result');\n    if (!w || !n || !r) continue;\n    st.lues++;\n    const term = tag('Termination').toLowerCase();\n    if (opt.incid && TERM_ECARTER.includes(term)) { st.incidents++; continue; }\n    const s = {'1-0':1,'0-1':0,'1\/2-1\/2':0.5}[r];\n    if (s === undefined) { st.incidents++; continue; }\n    if (opt.dedup) {\n      const coups = (b.slice(b.lastIndexOf(']')+1)\n        .match(\/[a-h][1-8][a-h][1-8][qrbn]?|[KQRBNO][-a-h1-8x+#=]*|[a-h]x?[a-h]?[1-8](=[QRBN])?\/g)||[]).join(' ');\n      const cle = w+'|'+n+'|'+tag('PlyCount')+'|'+coups;\n      if (vus.has(cle)) { st.doublons++; continue; }\n      vus.add(cle);\n    }\n    st.retenues++;\n    out.push({w, b:n, s});\n  }\n  return out;\n}\n\n\/\/ ================================================================= ajustement\n\/\/ Bradley-Terry logistique, nulles a un demi-point, avantage du trait ajuste\n\/\/ (equivalent de l'option -W d'Ordo). Les ancres sont FIXEES : dans les sorties\n\/\/ d'Ordo avec -m, les moteurs ancres ressortent a leur valeur exacte.\nfunction ajuster(parties, ancres, avecTrait){\n  const paires = new Map();\n  for (const g of parties) {\n    const k = g.w + '\\u0000' + g.b;\n    const e = paires.get(k) || {w:g.w, b:g.b, s:0, n:0};\n    e.s += g.s; e.n++; paires.set(k, e);\n  }\n  const P = [...paires.values()];\n  const noms = [...new Set(P.flatMap(g => [g.w, g.b]))].sort();\n  const R = {}; noms.forEach(n => R[n] = (ancres[n] !== undefined ? ancres[n] : 2600));\n  let W = avecTrait ? 30 : 0;\n\n  const par = {}; noms.forEach(n => par[n] = []);\n  P.forEach(g => { par[g.w].push(g); par[g.b].push(g); });\n  const att = (ra, rb, w) => 1\/(1 + Math.pow(10, (rb - ra - w)\/400));\n  const reel = {};\n  noms.forEach(n => { let s=0; for (const g of par[n]) s += (g.w===n) ? g.s : (g.n - g.s); reel[n]=s; });\n  const attendu = (n, r, w) => {\n    let s = 0;\n    for (const g of par[n]) s += (g.w===n) ? att(r, R[g.b], w)*g.n : (1 - att(R[g.w], r, w))*g.n;\n    return s;\n  };\n\n  let delta = 1e9;\n  for (let it = 0; it < 200 && delta > 0.02; it++) {\n    delta = 0;\n    for (const n of noms) {\n      if (ancres[n] !== undefined) continue;\n      let lo = R[n]-400, hi = R[n]+400;\n      for (let k=0; k<20 && attendu(n,lo,W) > reel[n]; k++) lo -= 400;\n      for (let k=0; k<20 && attendu(n,hi,W) < reel[n]; k++) hi += 400;\n      for (let k=0; k<40; k++){ const m=(lo+hi)\/2; if (attendu(n,m,W) < reel[n]) lo=m; else hi=m; }\n      const nv = (lo+hi)\/2;\n      delta = Math.max(delta, Math.abs(nv - R[n])); R[n] = nv;\n    }\n    if (avecTrait) {\n      let sw=0; P.forEach(g => sw += g.s);\n      const pred = w => P.reduce((a,g) => a + att(R[g.w], R[g.b], w)*g.n, 0);\n      let lo=-200, hi=200;\n      for (let k=0;k<40;k++){ const m=(lo+hi)\/2; if (pred(m) < sw) lo=m; else hi=m; }\n      W = (lo+hi)\/2;\n    }\n  }\n  return {R, W};\n}\n\n\/\/ ================================================================= rendu\nfunction construire(parties, R, W, opt){\n  \/\/ bilan par moteur et par adversaire\n  const rec = {};\n  const add = (a,o,s) => {\n    rec[a] = rec[a] || {};\n    const e = rec[a][o] || {g:0,s:0,w:0,d:0,l:0};\n    e.g++; e.s += s;\n    if (s === 0.5) e.d++; else if (s === 1) e.w++; else e.l++;\n    rec[a][o] = e;\n  };\n  parties.forEach(g => { add(g.w, g.b, g.s); add(g.b, g.w, 1 - g.s); });\n\n  const actifs = Object.keys(R).map(n => {\n    const r = rec[n] || {};\n    const j = Object.values(r).reduce((a,e) => a + e.g, 0);\n    const p = Object.values(r).reduce((a,e) => a + e.s, 0);\n    return {nom:n, r:R[n], joue:j, pts:p, pc: j ? 100*p\/j : 0};\n  }).filter(e => e.joue >= opt.minj).sort((a,b) => b.r - a.r);\n\n  const npy = actifs.filter(e => estPy(e.nom)).length;\n  let h = '';\n\n  \/\/ ---------------- classement ----------------\n  h += '<div class=\"lumn\" id=\"lumn-classement\">';\n  h += '<div class=\"lh\">Classement g\u00e9n\u00e9ral \u00b7 \u00e9chelle Ordo<\/div>';\n  h += '<h2 class=\"lt\">Performance Elo \u2014 tournois maison<\/h2>';\n  h += '<p class=\"lp\">Cotes calcul\u00e9es par ajustement global sur le PGN du laboratoire. '\n     + 'Les moteurs <span class=\"py\">en bleu<\/span> tournent sous adaptateur Python (ponts <em>eng2uci<\/em> \/ '\n     + '<em>shredder2uci<\/em>) : leur cote est celle de la version adapt\u00e9e, jamais celle du moteur d\\'origine. '\n     + 'Cliquer un moteur \u2731 fait d\u00e9filer vers son d\u00e9brief.<\/p>';\n  h += '<table><thead><tr><th class=\"n\">Rang<\/th><th>Moteur \/ Syst\u00e8me<\/th><th class=\"hwh\">Hardware<\/th>'\n     + '<th class=\"n\">Elo<\/th><th class=\"n\">Parties<\/th><th class=\"n\">Err \u00b1<\/th><\/tr><\/thead><tbody>';\n  actifs.forEach((e,i) => {\n    const py = estPy(e.nom);\n    let cell = '<span class=\"star\">\u2731<\/span> ' + celluleNom(e.nom, '#'+slug(e.nom));\n    if (py) cell += '<span class=\"pytag\">pont py<\/span>';\n    const err = sigma(e.pc\/100, e.joue);\n    h += `<tr><td class=\"n rk\">${i+1}<\/td><td>${cell}<\/td><td class=\"hw\">${esc(opt.hw)}<\/td>`\n       + `<td class=\"n elo${py?' py':''}\">${e.r.toFixed(0)}<\/td>`\n       + `<td class=\"n\">${e.joue}<\/td><td class=\"n\">\u00b1${err.toFixed(0)}<\/td><\/tr>`;\n  });\n  h += '<\/tbody><\/table>';\n  h += `<div class=\"hint\">\u2731 = d\u00e9brief cliquable \u00b7 <span class=\"py\">bleu<\/span> = moteur sous adaptateur `\n     + `(${npy} sur ${actifs.length}) \u00b7 avantage du trait ${W.toFixed(1)} \u00b7 ${opt.nb} parties retenues<\/div>`;\n  h += '<\/div>';\n\n  \/\/ ---------------- d\u00e9briefs ----------------\n  h += '<div class=\"lumn\" id=\"lumn-debriefs\">';\n  h += '<div class=\"lh\">D\u00e9briefs \u00b7 adversaire par adversaire<\/div>';\n  h += '<h2 class=\"lt\">D\u00e9brief de chaque gauntlet<\/h2>';\n  h += '<p class=\"lp\">Perf locale = cote de l\\'adversaire + 400\u00b7log\u2081\u2080(p\/(1\u2212p)). Sa dispersion refl\u00e8te la '\n     + 'transitivit\u00e9 du moteur. Les moteurs <span class=\"py\">en bleu<\/span> tournent sous adaptateur Python.<\/p>';\n  actifs.forEach(e => {\n    const r = rec[e.nom] || {}, py = estPy(e.nom);\n    const opps = Object.entries(r).sort((a,b) => (R[b[0]]||0) - (R[a[0]]||0));\n    const tg = Object.values(r).reduce((a,v) => a+v.g, 0);\n    const ts = Object.values(r).reduce((a,v) => a+v.s, 0);\n    const glob = tg ? 100*ts\/tg : 0, err = sigma(glob\/100, tg);\n    h += `<div class=\"card\" id=\"${slug(e.nom)}\">`;\n    h += `<h3 class=\"${py?'py':''}\">${esc(disp(e.nom))}${py?'<span class=\"pytag\">pont py<\/span>':''}<\/h3>`;\n    h += `<p class=\"mm\">Cote ${e.r.toFixed(0)} \u00b7 ${tg} parties \u00b7 ${glob.toFixed(1)} % \u00b7 err \u00b1${err.toFixed(0)}<\/p>`;\n    h += `<div class=\"elobox${py?' py':''}\"><span class=\"l\">Cote Ordo<\/span>`\n       + `<span class=\"v\">${e.r.toFixed(0)}<\/span><span class=\"s\">\u00b1 ${err.toFixed(0)}<\/span><\/div>`;\n    h += '<table><thead><tr><th>Adversaire<\/th><th class=\"n\">Cote<\/th><th class=\"n\">Pt.<\/th>'\n       + '<th class=\"n\">Score<\/th><th class=\"n\">%<\/th><th class=\"n\">W\/D\/L<\/th><th class=\"n\">Perf<\/th>'\n       + '<\/tr><\/thead><tbody>';\n    opps.forEach(([o,v]) => {\n      const p = v.s\/v.g, oc = R[o], pf = perfLocale(oc, p);\n      h += `<tr><td>${celluleNom(o)}<\/td><td class=\"n\">${oc!==undefined?oc.toFixed(0):'\u2014'}<\/td>`\n         + `<td class=\"n\">${v.g}<\/td><td class=\"n\">${(+v.s.toFixed(1))}\/${v.g}<\/td>`\n         + `<td class=\"n\">${(p*100).toFixed(1)} %<\/td><td class=\"n\">+${v.w}\/=${v.d}\/\u2212${v.l}<\/td>`\n         + `<td class=\"n\">${pf!==null?pf.toFixed(0):'\u2014'}<\/td><\/tr>`;\n    });\n    h += '<\/tbody><\/table>';\n    const cand = opps.filter(([o,v]) => v.g >= 10 && R[o] !== undefined);\n    if (cand.length) {\n      const meil = cand.reduce((a,b) => (b[1].s\/b[1].g > a[1].s\/a[1].g) ? b : a);\n      const dur  = cand.reduce((a,b) => (b[1].s\/b[1].g < a[1].s\/a[1].g) ? b : a);\n      h += `<div class=\"note\">Meilleur : ${(100*meil[1].s\/meil[1].g).toFixed(0)} % vs ${esc(disp(meil[0]))} \u00b7 `\n         + `plus dur : ${(100*dur[1].s\/dur[1].g).toFixed(0)} % vs ${esc(disp(dur[0]))}.<\/div>`;\n    }\n    h += '<div class=\"back\"><a href=\"#lumn-classement\">\u2191 retour au classement<\/a><\/div><\/div>';\n  });\n  h += '<\/div>';\n  return h;\n}\n\n\/\/ ================================================================= interface\nlet PARTIES = [], STATS = null, FRAGMENT = '';\nfunction lireAncres(t){\n  const o = {};\n  t.split('\\n').forEach(l => {\n    const m = l.match(\/^\\s*\"?(.*?)\"?\\s*,\\s*([\\d.]+)\\s*$\/);\n    if (m && m[1]) o[m[1].trim()] = parseFloat(m[2]);\n  });\n  return o;\n}\nfunction calculer(){\n  if (!PARTIES.length) { $('#etat').textContent = 'Aucun PGN charg\u00e9.'; return; }\n  const ancres = lireAncres($('#ancres').value);\n  if (Object.keys(ancres).length < 2) {\n    $('#etat').textContent = 'Il faut au moins deux ancres pour calibrer le classement.'; return;\n  }\n  $('#etat').textContent = 'Calcul en cours\u2026';\n  setTimeout(() => {\n    const t0 = Date.now();\n    const {R, W} = ajuster(PARTIES, ancres, $('#opt-white').checked);\n    const opt = {minj: Math.max(1, parseInt($('#minj').value,10)||1),\n                 hw: $('#hw').value.trim(), nb: PARTIES.length};\n    FRAGMENT = construire(PARTIES, R, W, opt);\n    $('#sortie').innerHTML = FRAGMENT;\n    $('#etat').textContent =\n      `${STATS.lues} partie(s) lue(s) \u00b7 ${STATS.retenues} retenue(s) \u00b7 ${STATS.incidents} incident(s) \u00b7 `\n      + `${STATS.doublons} doublon(s) \u00b7 ${Object.keys(R).length} moteur(s) \u00b7 avantage du trait ${W.toFixed(1)} \u00b7 `\n      + `calcul en ${((Date.now()-t0)\/1000).toFixed(1)} s`;\n  }, 30);\n}\nfunction charger(fs_){\n  const files = [...fs_];\n  PARTIES = []; STATS = {lues:0, retenues:0, incidents:0, doublons:0};\n  const vus = new Set(), opt = {incid:$('#opt-incid').checked, dedup:$('#opt-dedup').checked};\n  let n = 0;\n  $('#fichiers').innerHTML = files.map(f => `<span>${esc(f.name)}<\/span>`).join('');\n  files.forEach(f => {\n    const fr = new FileReader();\n    fr.onload = ev => {\n      PARTIES = PARTIES.concat(lirePGN(ev.target.result, opt, STATS, vus));\n      if (++n === files.length) {\n        $('#etat').textContent = `${STATS.lues} partie(s) lue(s), ${STATS.retenues} retenue(s). Cliquez \u00ab Calculer \u00bb.`;\n      }\n    };\n    fr.readAsText(f, 'utf-8');\n  });\n}\nconst depot = $('#depot');\ndepot.onclick = () => $('#fichier').click();\n$('#fichier').onchange = e => charger(e.target.files);\n['dragenter','dragover'].forEach(t => depot.addEventListener(t, e => {e.preventDefault(); depot.classList.add('on');}));\n['dragleave','drop'].forEach(t => depot.addEventListener(t, e => {e.preventDefault(); depot.classList.remove('on');}));\ndepot.addEventListener('drop', e => { if (e.dataTransfer.files.length) charger(e.dataTransfer.files); });\n$('#go').onclick = calculer;\n$('#copier').onclick = () => {\n  if (!FRAGMENT) { $('#etat').textContent = 'Rien \u00e0 copier : lancez d\\'abord le calcul.'; return; }\n  navigator.clipboard.writeText(FRAGMENT)\n    .then(() => $('#etat').textContent = 'Fragment HTML copi\u00e9 \u2014 collez-le dans un bloc HTML de WordPress.')\n    .catch(() => $('#etat').textContent = 'Copie refus\u00e9e par le navigateur ; utilisez \u00ab T\u00e9l\u00e9charger \u00bb.');\n};\n$('#telecharger').onclick = () => {\n  if (!FRAGMENT) { $('#etat').textContent = 'Rien \u00e0 t\u00e9l\u00e9charger : lancez d\\'abord le calcul.'; return; }\n  const css = document.querySelector('style').outerHTML;\n  const b = new Blob([css + '\\n' + FRAGMENT], {type:'text\/html;charset=utf-8'});\n  const a = document.createElement('a');\n  a.href = URL.createObjectURL(b); a.download = 'classement_et_debriefs.html'; a.click();\n  $('#etat').textContent = 'Fragment enregistr\u00e9 (CSS inclus).';\n};\n$('#ancres').value = ANCRES_DEFAUT;\n})();\n<\/script>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Calculateur de tournoi chess.lumn.eu \u00b7 outil Ordostat Chargez un ou plusieurs PGN : la page calcule les cotes par ajustement global (mod\u00e8le logistique, nulles \u00e0 un demi-point, avantage du trait ajust\u00e9), puis produit le classement et les d\u00e9briefs adversaire par adversaire. Tout se passe dans votre navigateur. D\u00e9posez vos PGN ici ou cliquez pour les&hellip;&nbsp;<a href=\"https:\/\/www.chess.lumn.eu\/wordpress\/2026\/09\/14\/calculateur-de-tournoi-copy\/\" class=\"\" rel=\"bookmark\">Read More &raquo;<span class=\"screen-reader-text\">Calculateur de tournoi &#8211; Copy<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":""},"categories":[5],"tags":[],"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"valterd","author_link":"https:\/\/www.chess.lumn.eu\/wordpress\/author\/valterd\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/www.chess.lumn.eu\/wordpress\/category\/tournaments\/\" rel=\"category tag\">Tournaments<\/a>","rttpg_excerpt":"Calculateur de tournoi chess.lumn.eu \u00b7 outil Ordostat Chargez un ou plusieurs PGN : la page calcule les cotes par ajustement global (mod\u00e8le logistique, nulles \u00e0 un demi-point, avantage du trait ajust\u00e9), puis produit le classement et les d\u00e9briefs adversaire par adversaire. Tout se passe dans votre navigateur. D\u00e9posez vos PGN ici ou cliquez pour les&hellip;&nbsp;Read&hellip;","_links":{"self":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4058"}],"collection":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/comments?post=4058"}],"version-history":[{"count":3,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4058\/revisions"}],"predecessor-version":[{"id":4061,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/posts\/4058\/revisions\/4061"}],"wp:attachment":[{"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/media?parent=4058"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/categories?post=4058"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.chess.lumn.eu\/wordpress\/wp-json\/wp\/v2\/tags?post=4058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}