Construire un dataset communautaire Trackmania pour la génération de niveaux et plus encore
Note : Cette version française a été générée par Claude Sonnet 5 (claude-sonnet-5) le 3 septembre 2026. La version originale en anglais est disponible ici.
TL;DR : J’ai empaqueté des années de maps et de replays de la communauté Trackmania venant de Mania Exchange dans un dataset plus amical pour les gens de la data/ML : plus de 850k maps sur deux jeux (Trackmania 2020 et Trackmania Nations Forever), environ 30 Go de Parquet. Il est sur Kaggle et Hugging Face. Je l’ai aussi utilisé pour expérimenter la génération de niveaux (fine-tuning de GPT-2, modèles de Markov, une approche à la TMTrackNN) et j’ai construit une petite application Gradio pour que n’importe qui puisse explorer des niveaux et en générer quelques-uns Tracksmith.
Ces derniers mois, j’ai beaucoup travaillé à optimiser et réactiver certains de mes projets data personnels sur AWS : beaucoup de devops/dataops, de packaging, et ainsi de suite. C’était un peu ennuyeux (un assistant de code rend ça moins pénible c’est sûr mais quand même). Alors j’ai voulu passer à autre chose. Une idée qui traînait dans mon backlog depuis longtemps tournait autour du jeu Trackmania.
Disclaimer : oui, il s’agit d’un jeu fait par mon employeur, Ubisoft, et oui, je travaille dans les équipes data qui accompagnent la production de jeux, mais je n’ai aucun lien avec Ubisoft Nadeo, le studio derrière Trackmania. Mon travail porte sur les productions de jeux canadiennes, et ce projet est purement sur mon temps personnel et mon propre argent ; la majorité du code sera rendue disponible pour que n’importe qui puisse l’utiliser chez lui. #sharingiscaring
Je ne serai pas le premier à explorer cette idée dans le monde : Donadigo a fait du bruit en 2018 quand il a sorti son projet TMTrackNN, où il a développé des modèles de deep learning pour produire des maps Trackmania. Il a publié son article/projet plus ou moins 6 mois après que j’ai commencé à travailler chez Ubisoft, et j’ai été tellement impressionné par ce que quelqu’un en dehors d’Ubisoft a réussi à faire tout seul, sans accéder à l’infrastructure d’Ubisoft.

Ça m’est resté en tête, mais je n’ai jamais eu le temps de creuser à fond. La sortie du nouveau Trackmania en 2020, ou des papiers comme MarioGPT, un modèle génératif de niveaux de Super Mario Bros sorti en 2023, ont gardé l’étincelle vivante dans mon backlog et je savais qu’à un moment je prendrais du temps pour explorer plus en détail les données et le projet.

Ces mois d’optimisation de code pour mon pipeline de données étaient le moment parfait pour faire quelque chose de différent et explorer les jeux Trackmania (plusieurs jeux maintenant) via sa communauté et les niveaux/replays qu’elle a partagés au fil des années. Avant de creuser dans les données et pour ceux qui ne connaissent pas Trackmania, laissez-moi faire un rapide tour de la franchise.
Trackmania mon amour
Trackmania est une franchise de jeu de course développée en 2003 par Nadeo (qui a été racheté par Ubisoft en 2009), qui est vraiment quelque part entre la course technique rapide et le contenu généré par les utilisateurs (UGC). Voici une rapide timeline des principales sorties.

De mon côté, Trackmania est entré dans ma vie autour de 2005, Trackmania Sunrise XL étant mon deuxième jeu vidéo sur PC après Warcraft 3, et je l’ai adoré. Après ça j’ai joué un peu à Trackmania Nations, puis Nations Forever, pendant mes premières années d’université. Avant de partir pour l’Angleterre je suis revenu à Trackmania sur console avec Turbo (acheté sur PS4 d’abord, et sur PC ensuite quand je suis parti pour l’Angleterre). Bien sûr, être chez Ubisoft m’a repoussé vers la franchise avec la sortie de Trackmania 2020, et j’ai eu mon temps pour explorer les premières saisons.

Trackmania peut être défini comme un jeu de conduite facile à prendre en main mais difficile à maîtriser : pour les contrôles vous avez juste à accélérer ou freiner, tourner à droite, tourner à gauche, et l’essentiel du gameplay réside dans le time attack, pour battre le meilleur score d’une map.
Comme je l’ai dit plus tôt, l’autre aspect au-delà de l’aspect course est la création de maps, car les jeux sont composés d’un éditeur de map qui donne la possibilité en quelques clics (dans un éditeur simple ou avancé) de créer une map de zéro en assemblant des blocs.

Au final, l’éditeur produit un fichier binaire dans un format GBX qui peut être partagé facilement avec n’importe qui. Ce même format est aussi utilisé pour les replays qui peuvent être sauvegardés et partagés de la même façon.
Sur les 20 ans d’existence de la franchise, une communauté a grandi et a commencé à partager des maps (et des replays des courses) en ligne, et l’une des principales plateformes est Mania Exchange, un ensemble de plusieurs sites hébergeant des maps et des replays faits par les communautés.
Mon idée était de voir comment je pouvais emballer et compiler le travail de la communauté dans un dataset plus « data/ML » friendly, et le rendre disponible à tout le monde, dans et hors de la communauté, pour expérimenter sur divers aspects allant de la compréhension des jeux vidéo, du level design mais aussi des aspects plus expérimentaux avec la génération de niveaux.
Emballer le travail de la communauté dans un dataset
Comme je l’ai dit, la communauté partage des fichiers GBX qui ne sont pas Python friendly mais plusieurs librairies communautaires existent pour lire ou écrire des fichiers GBX, alors j’ai commencé à construire des fonctions par-dessus pour interagir avec ces fichiers. Mes recherches m’ont poussé à me concentrer sur deux jeux en particulier : Trackmania 2020 (TM2020) et Trackmania Nations Forever (TMNF), qui sont les plus populaires (pour moi) et qui peuvent s’appuyer facilement sur les packages disponibles.
J’ai construit un petit scraper gentil qui collecte les maps de la communauté et leurs replays qui étaient disponibles jusqu’à mi-juillet 2026. L’objectif du scraper était de lire chaque fichier GBX et de le transférer dans un format JSON, pour être ensuite recompilé en Parquet afin d’être facilement manipulable par n’importe quel script Python.
Les datasets peuvent être trouvés à deux endroits, Kaggle ou Hugging Face, pour coller à plus d’intérêt d’exploration ou d’exploration machine learning (je référencerai Hugging Face par simplicité).
Le dataset est structuré avec un dossier par jeu et pour chaque jeu, voici un aperçu de l’organisation du dossier :
blocks_details_{game}.parquet: Une collection des statistiques autour de chaque bloc utilisé dans l’ensemble des maps du jeu (surtout le nombre de présences dans les maps et le nombre de blocs au total)tracks_details_{game}.parquet: les données par map (une ligne par map). Certaines colonnes viennent directement du site (nombre d’awards au moment de l’upload, le lien original) ; d’autres sont des métriques dérivées par des fonctions qui analysent la typologie de chaque map (nombre de virages, dénivelé, vitesse). Il y a aussi une colonnechunk_part, qui relie chaque map aux fichiersblocksettracesdécrits ci-dessous.blocks: un dossier de données de blocs par map, découpé en fichiers chunk nommésblocks_{game}_partNNNN.parquet. Chaque fichier couvre un chunk de maps, avec une ligne par bloc : le type/nom du bloc, son orientation et sa positiontraces: un dossier de données de replay par map, découpé en fichiers chunk nomméstraces_{game}_partNNNN.parquet. Chaque fichier couvre un chunk de replays, avec une ligne par état de la voiture pendant une course/un replay : son accélération, son freinage et son état de virage, sa position, et le bloc placé le plus proche (déjà associé pour vous, et l’extrait de code plus loin dans cet article reproduit cette étape pour que vous puissiez ajuster la règle d’association).
Dans le fichier blocks vous pouvez trouver un champ position_block_trace_order qui vous donne une idée de l’ordre dans lequel le bloc a été rencontré par le joueur en se basant sur les replays, ce qui peut aider à segmenter les blocs qui sont plus présents pour le « décor » de la map par rapport à ceux qui font partie de la trace sur la map. Vous pouvez trouver plus de détails et un aperçu plus profond des fichiers en regardant le dictionnaire de données.
Au final, le dataset représente plus de 850k maps sur les deux jeux (environ 1/3 pour TM2020, 2/3 pour TMNF). Compiler tout ça sur mon laptop a pris environ 3 semaines et a produit à peu près 30 Go. Pendant que ça tournait, j’ai commencé à explorer ce qu’il faudrait pour construire un générateur de niveaux à partir du dataset (au début sur un échantillon d’une semaine) pour voir où je pouvais commencer à concevoir quelque chose d’utile pour les autres qui travaillent sur le sujet de la génération de niveaux.
Ce que j’ai appris en essayant de générer des niveaux
Avant d’aller loin sur la génération de niveaux, soyons clairs : ce n’est que le début, alors ne vous attendez pas à un générateur de niveaux parfait qui produit les niveaux les plus agréables. J’ai essayé quelques approches. D’abord, j’ai imité l’approche du fine-tuning de GPT-2 de MarioGPT pour Trackmania, avec des résultats intéressants mais rien de concluant. Ensuite je suis revenu au premier amour de mon stage de fin d’études, un modèle basé sur les chaînes de Markov pour les transitions bloc à bloc, pas mal, mais loin d’être parfait. Finalement j’ai fusionné mes apprentissages et je suis retourné à l’approche TMTrackNN, que je trouve plutôt maligne. Tout mon apprentissage est documenté pour ce projet alors n’hésitez pas à y jeter un œil plus en détail.
De cette expérience et avant de creuser dans le générateur que j’ai construit, je voulais récapituler les principaux apprentissages / trucs et astuces qui sont sortis de cette expérimentation et qui peuvent aider ceux qui veulent travailler sur ce genre d’application avec ce dataset.
Pour soutenir l’exploration du dataset j’ai construit ce notebook sur Kaggle pour avoir un aperçu de ce qu’il y a dedans.
Curez les maps sur lesquelles vous entraînez
Utilisez tracks_details_{game}.parquet autant que possible pour filtrer les maps que vous allez utiliser pour construire un générateur de niveaux. Pouvoir filtrer toute map sans replays, ou sans awards, peut aider à appliquer une certaine curation sur l’ensemble des maps.
Il y a quelques distributions de certaines métriques liées aux maps sur les deux jeux.


Notes : Les deux jeux sont similaires en termes de vitesse, de dénivelé ou de configuration mais on peut clairement voir une différence en termes de nombre de blocs.
Les x,y,z des maps ne sont pas les x,y,z des traces
C’est important : les positions des blocs et les positions des replays n’utilisent pas le même référentiel. Les fichiers blocks utilisent un référentiel et la table traces un autre. Vous pouvez convertir de l’un à l’autre avec une transformation simple.
Voir l'extrait de code
"""
Trackmania stores blocks on an integer grid; replays store the car as world-space
meters on the same axes. The grid cell size is the same for TMNF and TM2020:
X (horizontal) : 32 m
Y (vertical) : 8 m
Z (horizontal) : 32 m
The helpers below assume the two frames also share an origin. In practice a game
can add a constant offset (e.g. the editor's default ground sits at block Y = 9),
so check the offset against your own parsed data before trusting absolute positions.
"""
import numpy as np
BLOCK_SIZE = np.array([32.0, 8.0, 32.0]) # meters per grid cell (x, y, z)
def block_to_world(bx, by, bz):
"""Grid cell (from the `blocks` table) -> world-space center of that cell,
in the same frame as the `x/y/z` columns of the `traces` table."""
return (np.array([bx, by, bz]) + 0.5) * BLOCK_SIZE
def world_to_block(wx, wy, wz):
"""World-space point (from the `traces` table) -> the integer grid cell it
sits in (the `x/y/z` columns of the `blocks` table)."""
return np.floor(np.array([wx, wy, wz]) / BLOCK_SIZE).astype(int)
# --- Example: attach the nearest placed block to every replay sample of a track ---
# (this reproduces the dataset's precomputed nearest-block column, so you can
# swap in your own matching rule)
#
# blocks_df : rows from blocks_{game}_partNNNN.parquet for one track_id
# traces_df : rows from traces_{game}_partNNNN.parquet for the same track_id
block_centers = (blocks_df[["x", "y", "z"]].to_numpy() + 0.5) * BLOCK_SIZE
sample_pos = traces_df[["x", "y", "z"]].to_numpy()
# NOTE: block_centers carry the +0.5 Y offset, so this match is biased vertically
# (the drivable surface sits near a block's base, not its center)
# pairwise distances, then argmin per sample (fine for one track; use a KDTree at scale)
dist = np.linalg.norm(sample_pos[:, None, :] - block_centers[None, :, :], axis=2)
nearest_idx = dist.argmin(axis=1)
traces_df["nearest_block_name"] = blocks_df["name"].to_numpy()[nearest_idx]
traces_df["nearest_block_dist_m"] = dist[np.arange(len(traces_df)), nearest_idx]
Limitez les blocs avec lesquels vous générez
Le fichier blocks_details_{game}.parquet donne une idée de ce qui est populaire, mais chaque jeu a énormément de blocs possibles : certains viennent de Nadeo, certains sont entièrement construits par la communauté. J’ai dû restreindre la génération à une liste curée, parce que certains blocs ne sont plus valides et font planter le jeu au chargement (Ça m’a pris des jours à diagnostiquer).
Prédire la trace de la voiture avant la map
J’ai exploré ça surtout avec une approche de Markov mais mon hypothèse est que pouvoir générer des états plausibles de la voiture (GOING UP, GOING DOWN, TURN RIGHT, TURN LEFT, ACCELERATE, DECELERATE) pourrait aider à orienter la génération dans une direction plus claire. Vous pouvez être très large avec des statuts haut niveau comme je l’ai mentionné plus tôt ou être plus granulaire et prédire la valeur exacte d’accélération, de frein, d’inclinaison.
Inspecter les maps et les replays ensemble
Pouvoir analyser rapidement une map que vous avez construite et un replay que vous avez fait est essentiel, et peut vous donner beaucoup d’insights sur l’organisation d’une map. Par exemple, voici une capture d’écran de la page de mon compte Steam, et comme vous pouvez le voir, Trackmania Nations Forever a été beaucoup ouvert récemment (le temps est important parce que le jeu a été laissé ouvert beaucoup de nuits).

Creuser dans vos propres données est essentiel, alors jouez au jeu et avec l’éditeur.
Représenter une map en 3D ou en JSON avec des ids par bloc aide aussi, parce que ça permet d’interagir avec la map et un replay ensemble. Quand vous travaillez avec un agent de code, avoir l’id du bloc aide beaucoup car vous pouvez pointer les configurations de blocs exactes qui sont fausses en vous référant à leur id de bloc que vous pouvez voir dans la visualisation 3D et dans le JSON.
La géométrie est la clé
Une map Trackmania est assez simple en soi : c’est juste une chaîne de blocs connectés, qui ont une position et une orientation, donc prédire le prochain bon bloc n’est pas si difficile ; la difficulté est sur la façon dont les blocs peuvent être connectés ensemble, car ils ont tous des formes et des points d’entrée variés. Mon point de douleur était sur les virages avec leur taille et leur connexion aux autres blocs.

Au-delà de ces apprentissages voyons ce que j’ai produit.
Tracksmith : un outil pour l’exploration et la génération de niveaux
Alors oui, si vous êtes encore là après toute cette lecture, j’ai quelque chose pour vous. Ça s’appelle Tracksmith (Trackmania + Blacksmith) : une application Gradio où vous pouvez inspecter une map et son replay associé et, plus important encore, vous pouvez générer des maps pour TMNF ou TM2020.

L’application est disponible sur Hugging Face Spaces, et utilise une partie du code déployé pour traiter les GBX afin de construire le dataset. Pour la génération de niveaux c’est un TMTrackNN revisité, en PyTorch avec plus de données et qui peut produire des niveaux pour les deux jeux.

Encore une fois, la génération est loin d’être la plus créative mais c’est un début qui peut donner un coup de pouce à n’importe qui qui veut commencer à creuser.
La suite
Ce projet était surtout une histoire de partage du travail de la communauté Mania Exchange, avec cette communauté mais aussi avec tous les autres. J’espère que les gens y trouveront des choses utiles, et que ça remettra un coup de projecteur sur la créativité de la communauté et sur à quel point les jeux Trackmania peuvent être modulaires.
Si j’avais plus de temps, j’explorerais comment automatiser le test des maps avec des bots car conduire dans Trackmania depuis Python est déjà possible (voir les vidéos de Yann Bouteiller ou Yoshtm sur YouTube), donc brancher ça dans une boucle de test semble faisable.
Encore une fois, ce dataset n’est pas seulement pour la génération de niveaux. Plein d’autres choses peuvent en sortir : compréhension du joueur, analyse de level design, systèmes de recommandation, génération de métadonnées, ce que vous voulez. Alors n’hésitez pas à l’utiliser, et à citer le dataset/l’article.
Pour l’instant, la première itération que je voulais construire est faite : le dataset et assez de code pour relancer un projet de génération de niveaux. De mon côté, j’ai quelques idées d’exploration autour de la génération de niveaux (de bonnes vieilles techniques face à des approches plus modernes à base de modèles de langage) mais rien de concret pour le moment.
Je veux souligner encore une fois le travail incroyable de Nadeo (et Ubisoft .. je sais buttlicker :)) autour de la création de cette franchise incroyable, la communauté Mania Exchange pour tout le travail et le dévouement qu’ils y ont mis et tous les contributeurs de code comme Donadigo qui m’ont permis de rendre mon entrée dans ces données plus facile.
Références
- Mania Exchange — mania.exchange
- Trackmania Community Tracks and Telemetry (dataset) — Kaggle
- Trackmania Community Tracks and Telemetry (dataset) — Hugging Face
- Dictionnaire de données du dataset — Hugging Face
- Tracksmith (application Gradio) — Hugging Face Spaces
- Notes d’apprentissage de Tracksmith (LEARNING.md) — Hugging Face Spaces
- TMTrackNN — donadigo.com
- Donadigo — donadigo.com
- MarioGPT — GitHub
- Esports World Cup 2026 — esportsworldcup.com
- Notebook d’analyse exploratoire des données — Kaggle
- Yann Bouteiller sur TMRL (vidéo) — YouTube
- Yoshtm — YouTube
Citation
Si vous avez trouvé cet article utile, merci de le citer ainsi :
Daignan, Jean-Michel. (Aug 2026). Building a Trackmania Community Dataset for Level Generation & More. the-odd-dataguy.com. https://www.the-odd-dataguy.com/en/blog/2026/08/13/trackmania-dataset/.
ou
@article{daignan2026trackmania,
title = {Building a Trackmania Community Dataset for Level Generation & More},
author = {Daignan, Jean-Michel},
journal = {the-odd-dataguy.com},
year = {2026},
month = {Aug},
url = {https://www.the-odd-dataguy.com/en/blog/2026/08/13/trackmania-dataset/}
}