Sources¶
Tout ce que ce cours affirme vient de l'un des trois blocs ci-dessous : nos rapports internes, nos sources de données, ou la littérature. Le statut de chaque bloc est différent, et le cours le signale à chaque fois.
1 · Sources primaires internes¶
Ce sont les documents qui portent nos mesures. Ils vivent dans le dépôt du
projet, à la racine /workspace/cs2-vrs-major-predictor. Chaque dossier
d'approche contient au minimum un report.json (les mesures brutes, lisibles par
une machine) et, sauf exception, un README.md (le récit en trois paragraphes).
Le protocole et le classement¶
| Fichier | Contenu |
|---|---|
scratch/ml/BRIEF.md |
Le protocole commun du concours : paliers à battre, quatre règles non négociables, données disponibles |
scratch/ml/LEADERBOARD.md |
Le classement final des 11 approches et les repères |
Les onze approches classées¶
| Approche | Dossier | Ce qu'on y trouve de particulier |
|---|---|---|
| logreg | scratch/ml/logreg/ |
logistique codée à la main, variante « probabilité de manche » rejetée, poids commentés |
| bayes | scratch/ml/bayes/ |
grilles de réglage Glicko-2 et TrueSkill, horloges en prédicteurs purs, tables de calibration |
| stats | scratch/ml/stats/ |
extraction des tables de statistiques joueurs de 9 112 pages, player_stats.pkl |
| maps | scratch/ml/maps/ |
veto, scores par carte avec mi-temps, head-to-head daté, maps_data.pkl |
| stack | scratch/ml/stack/ |
quatre configurations d'empilement, diagnostics de calibration forcée |
| final | scratch/ml/final/ |
fusion des trois familles, comparaison logistique / XGBoost / mélange, features_final.pkl |
| push | scratch/ml/push/ |
~60 configurations issues de la littérature, trois passes de validation, mapfeats.py |
| squeeze | scratch/ml/squeeze/ |
quatre chantiers d'extraction du cache, rang mondial daté, rank_data.pkl |
| assault | scratch/ml/assault/ |
décomposition du saut, build_dataset.py, assault_features.pkl, run.log |
| lastmile | scratch/ml/lastmile/ |
quatre chantiers, trois morts chiffrées, premiers segments, model_lastmile.pkl |
| segmodel | scratch/ml/segmodel/ |
le leader : grille de 40 combinaisons, segments du holdout, model_segmodel.pkl |
Les approches hors classement¶
| Approche | Dossier | Statut |
|---|---|---|
| gbdt | scratch/ml/gbdt/ |
report.json complet, pas de README — réglage de trois bibliothèques d'arbres, résultat 0.2331 |
| hardcore | scratch/ml/hardcore/ |
hypothèse « le simple bat le riche sur le segment dur » réfutée, grille de 110 configurations |
| ultimate | scratch/ml/ultimate/ |
économie rejouée sur les manches unifiées : couverture par jeu, 13 vainqueurs discordants listés, verdict « INFIRMÉ » |
| lastcard | scratch/ml/lastcard/ |
wtsm, chantier de clôture : construction de l'horloge, corrélation à l'ancienne, seuil de bruit, verdict « la validation mentait » (pas de README, report.json + run.log) |
| scout | scratch/ml/scout/ |
reconnaissance des sources de données : ce qui est mort, ce qui est viable, avec les preuves |
| harvest | scratch/ml/harvest/ |
industrialisation des trois gisements externes, volumes, taux d'appariement, caveats temporels |
Les six chantiers de confrontation au marché¶
Menés après la clôture de la campagne, ils portent la partie Contre le marché. Aucun n'ajoute de ligne au classement : ce sont des mesures, pas des modèles. Tous sont hors ligne et reproductibles, et chacun commence par reproduire le leader à 0.2041 / 67,9 % sous assertion.
| Chantier | Dossier | Ce qu'il établit |
|---|---|---|
| bookpool | scratch/ml/bookpool/ |
les cinq définitions du pool comparable, figées et horodatées avant la première métrique ; le pool coté est plus dur ; le spécialiste « tier 1 » ne sauve rien |
| odds | scratch/ml/odds/ |
la découverte des cotes dans le champ bet_updates, le diagnostic de contamination in-play, le vrai palier du marché sur nos matchs (0.2094 / 65,6 %) |
| vsmarket | scratch/ml/vsmarket/ |
la cote en feature (morte), le mélange à deux régimes (0.2036), la courbe plate de \(w\), le pool de 903 matchs |
| oddsmax | scratch/ml/oddsmax/ |
l'appariement élargi (+525 matchs cotés), l'inversion du verdict, le diagnostic de collecte — pas de README, tout est dans report.json, measure.json et verify.json |
| fairfight | scratch/ml/fairfight/ |
la mesure à fenêtre de fit constante, le balayage qui chiffre le biais (0.0104 de Brier), les deux verdicts finaux |
| maxacc | scratch/ml/maxacc/ |
l'angle mort de l'exactitude : seuil de décision, objectifs orientés exactitude, zone d'indécision, cible des 70 % |
| Objectif | Commande |
|---|---|
| Rejouer la découverte des cotes et le diagnostic in-play | python scratch/ml/odds/code/run.py (~35 s) |
| Rejouer la cote en feature et le blend | python scratch/ml/vsmarket/run.py (~22 s) |
| Rejouer la mesure à armes égales | python scratch/ml/fairfight/run.py (~40 s, reprenable) |
Comment lire un report.json
Ces fichiers ont tous la même structure : une section protocol (tailles des
jeux, nombre de passes de holdout, leader à battre), une section
validation (chaque configuration testée avec son Brier), un
holdout_table (les mesures finales), une calibration_final par tranches,
et souvent un verdict en clair.
C'est cette structure qui rend les échecs réutilisables : on peut retrouver le chiffre exact d'une idée abandonnée sans relancer quoi que ce soit.
Le code de production¶
| Fichier | Rôle |
|---|---|
vrs/predict.py |
Le modèle de probabilité de victoire de départ, le balayage chronologique de référence, la définition du holdout de 42 jours et la fonction de métriques commune |
vrs/winprob.py |
L'industrialisation du leader : le blend segmodel en production dans le simulateur |
vrs/model.py |
Le moteur de classement porté depuis le code officiel |
Les documents de campagne¶
| Fichier | Contenu |
|---|---|
docs/corrections-moteur-2026-08.md |
Les dix corrections du moteur de classement, chacune avec symptôme, cause, correctif et mesure — et les quatre leçons transverses sur l'anachronisme |
scratch/drift/rapport.md |
L'enquête complète sur le patchwork de pagination : symptôme, cause racine, bisection par la donnée, simulation de contrôle, correctifs, protocole de mesure |
docs/superpowers/specs/2026-08-22-vrs-major-predictor-design.md |
Le design initial du simulateur |
L'historique Git¶
Le dépôt porte la chronologie exacte de la campagne. Les commits qui jalonnent la partie modélisation, dans l'ordre :
| Commit | Horodatage (UTC) | Objet |
|---|---|---|
545e94f |
24/08 08:36 | Monte Carlo des tournois restants — le besoin de probabilités |
5e9425c |
24/08 18:40 | Premier modèle appris, benchmarké contre Glicko |
fbe621d |
24/08 18:55 | L'Elo à marge |
3f5a0ed |
24/08 20:05 | Concours de modèles : six agents, trois familles |
c607a9d |
24/08 20:33 | Missions scout et push |
0a88d0d |
25/08 03:57 | Assaut final : 0.2054 |
a69a5a0 |
25/08 04:07 | lastmile : 0.2044 et la carte des poches de prévisibilité |
a46bf60 |
25/08 04:54 | segmodel confirmé : 0.2041 |
3e54eef |
25/08 05:35 | Industrialisation du leader en production |
9ce4864 |
25/08 06:05 | Continuité des pages de résultats |
c55bb97 |
25/08 06:59 | Hypothèse « simple sur le segment dur » réfutée |
a4f4a68 |
25/08 07:05 | Sentinelle anti-patchwork |
4be6253 |
25/08 07:41 | Correctif de la fuite d'arrêt anticipé du modèle de repli |
adfd8bc |
25/08 08:11 | Manches unifiées sur douze mois — et correction à la source du tri de tuples à dictionnaires |
38fec04 |
25/08 08:23 | Exclusion des 13 vainqueurs discordants de l'état d'économie |
904744c |
25/08 09:28 | wtsm infirmé — clôture de la campagne |
Les messages de ces commits contiennent des décompositions chiffrées qui ne
figurent nulle part ailleurs — notamment les gains marginaux d'assault, les deux
informations de campagne de lastmile, et le constat de clôture de 904744c :
« troisième fois que l'unique passe holdout tranche contre elle en fin de
campagne ».
Les chantiers ouverts à la clôture de rédaction¶
Ces dossiers ne contiennent clos après rédaction : couverture insuffisante (mapstats, 5,9 %) et non-viable (headless, Turnstile managed) — rapports dans scratch/ml/{mapstats,headless}/
Deux chantiers ont été lancés après la clôture de la campagne et n'avaient rien produit au moment où ces pages ont été écrites. Ils sont listés pour que le lecteur puisse aller voir s'ils ont abouti depuis — pas comme sources d'un chiffre de ce cours.
| Dossier | Objet | État à la rédaction |
|---|---|---|
scratch/ml/mapstats/ |
inventaire de la couverture archivée des pages de statistiques par carte, avant toute collecte massive | script d'inventaire écrit, clos après rédaction : couverture insuffisante (mapstats, 5,9 %) et non-viable (headless, Turnstile managed) — rapports dans scratch/ml/{mapstats,headless}/ |
scratch/ml/headless/ |
sonde d'un navigateur sans interface contre la protection anti-robot | dossier créé, clos après rédaction : couverture insuffisante (mapstats, 5,9 %) et non-viable (headless, Turnstile managed) — rapports dans scratch/ml/{mapstats,headless}/ |
2 · Sources de données externes¶
Statut : testées et documentées par la mission scout, avec les preuves dans
scratch/ml/scout/report.json.
Sources exploitées¶
| Source | Accès | Ce qu'elle a apporté |
|---|---|---|
HLTV — https://www.hltv.org |
cache local de ~16 000 pages, aucune requête pendant la campagne | résultats, compositions, statistiques par joueur, veto, scores par carte, historique de classement daté |
bo3.gg — API REST publique, https://bo3.gg/api/v1 |
ouverte, sans clé, ~1 requête/seconde | 209 482 manches détaillées (économie, pistolets, premiers kills, échanges, clutchs) et 40 023 matchs d'historique depuis janvier 2024 |
Wayback Machine — http://web.archive.org/cdx/search/cdx puis https://web.archive.org/web/<timestamp>id_/<url> |
ouverte, cadence volontairement lente | 966 clichés datés de statistiques de carrière, contournant le blocage du sous-domaine de statistiques |
Sources testées et écartées¶
| Source | Test effectué | Verdict |
|---|---|---|
HLTV /stats en direct |
six empreintes de navigateur récentes, plus session réchauffée avec cookies | mort — 403 et challenge Cloudflare limité au chemin /stats/* ; il faudrait exécuter le JavaScript du challenge |
m.hltv.org |
résolution DNS | mort — même adresse IP que le site principal, pas de variante mobile |
| Liquipedia (API MediaWiki) | requête action=parse avec agent identifiant |
joignable, mais faible valeur statistique face à bo3.gg ; à garder pour désambiguïser des compositions |
| PandaScore | requête sans jeton | clé requise ; les cotes sont réservées aux offres payantes. Le champ ps_id de bo3.gg rendrait l'intégration triviale le jour venu |
esports-charts (escharts.com) |
requête directe | mort — 403, et l'audience est de toute façon peu prédictive |
Jeu de données ESTA (dépôt pnxenopoulos/esta) |
interrogation de l'API GitHub | mort pour notre usage — données de l'ère CS:GO, aucun recouvrement avec notre fenêtre |
| Cotes dans le cache HTML | recherche des motifs odds, bet, provider sur 30 pages de match |
mort — le mot n'apparaît que dans les commentaires de forum ; les cotes sont injectées en JavaScript géolocalisé |
La source de cotes finalement retenue¶
| Source | Accès | Ce qu'elle a apporté |
|---|---|---|
bo3.gg, champ bet_updates du listing /api/v1/matches |
déjà sur disque, zéro requête réseau | 1 428 matchs cotés d'avant-match de 2025-10 à 2026-08, cote décimale par équipe, identifiant d'équipe pour l'orientation |
Trois précisions qui conditionnent l'usage de cette source. Elle mélange le
marché d'avant-match (/en/line/) et le marché en direct (/en/live/), ce
dernier étant une fuite du futur à mettre en quarantaine. Elle ne porte
aucun horodatage : impossible de savoir s'il s'agit d'une cote d'ouverture ou
de clôture. Et elle ne couvre qu'un opérateur, pas un consensus.
Le diagnostic de collecte a par ailleurs établi qu'il n'y a rien de plus à aller chercher chez cette source : aucun point d'accès dédié aux cotes n'existe, le détail d'un match ne porte pas d'autre information que le listing, et réinterroger trente matchs ne change rien.
Limite importante — ce que le verdict sur les cotes ne couvre pas
Le palier bookmaker n'est plus « sans données » : il est mesuré sur nos propres matchs. Mais la mesure porte sur un seul opérateur, des cotes non horodatées, un pool choisi par lui, et onze mois — et elle évalue une qualité probabiliste, jamais un rendement. Aucune stratégie de mise n'a été simulée, et la marge de 6 à 8 % de l'opérateur n'a pas été franchie.
3 · Littérature citée pendant la campagne¶
Statut : résultats et méthodes d'autres auteurs, cités par nom dans nos rapports et notre code.
Aucune URL n'est donnée ici, et c'est volontaire
Ces références sont citées par leur nom dans nos rapports et nos
commentaires de code — vrs/predict.py, scratch/ml/push/mapfeats.py,
scratch/ml/push/run.py — sans lien. Aucune URL n'a été inventée pour cette
page : une référence bibliographique fabriquée serait pire que l'absence de
référence.
Pour retrouver ces travaux, partez des noms et des chiffres ci-dessous.
Repères de performance¶
Mémoire universitaire de Lund sur CS2. Une régression logistique atteignant
une log-loss de 0,62 et 65 % d'exactitude. Cité dans vrs/predict.py et
enregistré dans data/winprob_report.json sous la clé lund_cs2_logreg. C'est
le repère qui a confirmé deux fois que la frontière était quasi linéaire.
Étude CS:GO sur 11 271 matchs et 142 features. XGBoost identifié comme
meilleur modèle, TrueSkill utilisé comme feature. Cité dans l'en-tête de
vrs/predict.py. C'est cette référence qui a motivé l'exploration TrueSkill du
concours — laquelle a produit la meilleure horloge du projet.
Bookmakers. Environ 68,7 % d'exactitude et 0,198 de Brier. Cité dans
vrs/predict.py et dans data/winprob_report.json sous la clé bookmakers.
Le même fichier prend soin de préciser que nos chiffres ne sont pas
directement comparables : notre pool mêle tier 1 et tier 3.
Ce repère a depuis été remplacé, sur nos matchs, par une mesure directe :
0.2094 de Brier et 65,6 % d'exactitude pour un opérateur réel sur les 160 matchs
cotés du holdout (scratch/ml/odds/report.json). Le chiffre de littérature reste
cité dans ce cours, mais uniquement comme repère externe — jamais comme la barre
de notre échantillon.
Méthodes appliquées¶
Elo à marge de victoire, façon FiveThirtyEight. Mise à jour modulée par
\(\ln(1 + \text{marge})\) avec une correction d'autocorrélation en
\(2{,}2 / (0{,}001 \cdot \text{diff} + 2{,}2)\). Recette issue des travaux de
FiveThirtyEight sur le hockey (NHL), adaptée ici à la marge en manches par
carte. Documentée en tête de scratch/ml/push/mapfeats.py. Adoptée.
pi-ratings, Constantinou & Fenton. Système de rating conçu pour prédire
l'écart de score plutôt que la victoire, avec une mise à jour amortie en
logarithme de l'erreur. Cité dans scratch/ml/push/mapfeats.py. Adopté —
la feature pi_diff_a figure dans tous les leaders à partir de push.
Elo par surface, Tennis Abstract / Kovalchik. L'idée d'un rating par surface
mélangé moitié-moitié avec un rating global, transposée ici à un Elo par
carte mélangé avec un Elo toutes cartes. Citée dans
scratch/ml/push/mapfeats.py. Adoptée sous forme d'horloge, mais la
recomposition de série qui devait en découler a échoué.
GBDT→LR, Facebook 2014. Feuilles d'un petit ensemble d'arbres transformées
en variables indicatrices, données à une régression logistique. Citée dans
scratch/ml/push/run.py. Rejetée — 0.2405 et au-delà contre 0.2263 pour la
logistique nue.
Modélisation à la manche. L'idée de modéliser l'issue de chaque manche puis de recomposer la série est une pratique répandue chez les fournisseurs de données esport, dont PandaScore. Elle a été testée sous deux formes ici (logistique de carte gagnée, logistique fractionnaire de manches avec course au premier à 13) et rejetée : 0.2303 à 0.2379 en validation, le veto attendu étant trop bruité. À noter : le projet n'a pas eu accès à une publication détaillant cette approche, seulement à sa description générale — c'est la référence la moins solidement établie de cette liste.
4 · Reproduire les mesures¶
Les commandes exactes figurent en tête de chaque script et dans les README des approches. Les points d'entrée principaux :
| Objectif | Commande |
|---|---|
| Reconstruire les features du leader | python scratch/ml/assault/build_dataset.py (~15 min) |
| Rejouer le leader et ses segments | python scratch/ml/segmodel/run_segmodel.py |
| Rejouer la réfutation de hardcore | python scratch/ml/hardcore/run.py |
| Rejouer la fusion du concours | python scratch/ml/final/run.py (~2 min) |
Deux garde-fous sont incorporés à ces scripts et méritent d'être connus : le mode hors ligne est forcé à l'import (aucune requête ne peut partir), et chaque chantier commence par reproduire le résultat du leader précédent avec une assertion qui échoue si le chiffre ne retombe pas.
Retour aux annexes · Glossaire · Tableau récapitulatif