Le pitch
Un jeu de course qui ne cherche pas le réalisme graphique, mais le réalisme physique. Pas un jeu d’arcade de plus où on tient le drift au frein à main. Ici les pneus ont un vrai comportement, et le pilotage demande de la finesse. J’aime les bagnoles, j’aime la simulation, et l’idée c’était de voir jusqu’où je pouvais pousser un modèle de conduite tout seul, sans moteur de jeu commercial derrière.
L’esthétique PSX (textures qui wobblent, vertex snapping, basse résolution), c’est la direction artistique que j’ai en tête depuis le début. Rien de tout ça n’est codé, autant le dire tout de suite. Le rendu actuel, c’est ce que Bevy puis Godot donnent par défaut. Tout le temps est parti dans les pneus.
La physique, d’abord
Le point de départ, c’est Rust, Bevy 0.18 pour l’ECS et le rendu, avian3d 0.5 pour le corps rigide. La simulation véhicule tourne dans un FixedUpdate à 240 Hz. C’est cher, mais en dessous les forces de pneu deviennent instables et la voiture se met à osciller toute seule.
Le modèle de pneu est un Pacejka PAC2002 (Magic Formula 5.2). Les deux entrées fondamentales sont le slip ratio (κ, le glissement longitudinal) et le slip angle (α, l’angle de dérive). La formule en sort une force longitudinale, une force latérale et un couple d’auto-alignement. Le combined slip, lui, passe par les fonctions G de PAC2002. Mettre les gaz en plein virage mange l’adhérence latérale, freiner en courbe aussi. C’est ce qui fait qu’on sent la limite au lieu de la franchir d’un coup.
Autour de ça, tout ce qui fait qu’un pneu n’est pas une constante :
- Thermique. Trois zones de surface sur la largeur de la bande de roulement (interne, milieu, externe) plus une température de carcasse. Le carrossage décide de la répartition de la charge entre les zones. En carrossage négatif, l’extérieur travaille et chauffe plus. Le chauffage vient du glissement et de l’hystérésis de la gomme, le refroidissement de trois sources séparées (convection naturelle, flux d’air créé par la rotation du pneu, vitesse de la voiture).
- Pression. La carcasse chauffe l’air enfermé dedans, la pression monte selon la loi de Gay-Lussac, et l’adhérence se décale avec. Un pneu froid n’a pas la même pression qu’un pneu à température.
- Usure. Modèle énergétique, avec un multiplicateur d’ablation qui monte avec la vitesse de glissement. Un burnout n’use pas linéairement.
- Longueur de relaxation. Le pneu ne construit pas sa force instantanément. L’angle de dérive passe par un filtre passe-bas du premier ordre dont la constante de temps dépend de la vitesse.
- Surfaces. Chaque mesh de circuit porte un type (bitume neuf, vieux, sale, vibreur, herbe, sable, bac à graviers, gazon synthétique) avec son multiplicateur d’adhérence, sa résistance au roulement et son amplitude de bosses.
Les trois premiers se combinent bêtement, en multipliant :
/// Combined grip scale = thermal × wear × pressure.
pub fn grip_scale(temp_c: f32, wear: f32, pressure_bar: f32, optimal_bar: f32, optimal_temp_c: f32) -> f32 {
thermal_grip_scale(temp_c, optimal_temp_c)
* (1.0 - 0.3 * wear.clamp(0.0, 1.0))
* pressure_grip_scale(pressure_bar, optimal_bar)
}
Le reste de la voiture suit : suspension à double triangulation avec gains de carrossage et de pincement, ressort plus butée plus amortisseur, chaîne moteur vers embrayage vers boîte vers différentiel, turbo, ABS. S’ajoute le support volant à retour de force, en DirectInput sous Windows et en evdev sous Linux, avec un lissage exponentiel sur la force pour enlever le tremblement sans tuer le ressenti de la route.
38 commits entre fin février et mi-avril. À ce stade, la voiture se conduisait vraiment.
Le passage à Godot
Le 13 avril, j’ai tout porté sous Godot 4.6 + C#.
La raison n’a rien à voir avec la physique. Je voulais un éditeur visuel. Sous Bevy, chaque scène, chaque bout d’UI, chaque placement de nœud se code. Ça va tant qu’on écrit de la simulation, mais dès qu’il faut assembler un circuit, poser des caméras et câbler un HUD, on passe son temps à recompiler pour déplacer un label. Godot met la scène au centre. L’éditeur devient la source de vérité, et le C# ne garde que la logique d’exécution.
Par contre, pas question de réécrire la physique en C#. Elle est restée en Rust, compilée en GDExtension (rust_physics/), qui expose deux classes au moteur, PhysicsSim pour la simulation d’une voiture et WheelDevice pour le volant et son retour de force. Le contrat est volontairement bête. Un tableau de flottants dans un sens, un tableau dans l’autre, appelé à chaque tick physique depuis CarController._IntegrateForces :
#[func]
fn step(&mut self, inputs: PackedFloat32Array, dt: f32) -> PackedFloat32Array {
if inputs.len() < 54 {
return PackedFloat32Array::new();
}
let inp = decode_inputs(&inputs, self.auto_clutch);
let outputs = sim::step(&mut self.state, &inp, dt, &self.config);
encode_outputs(&outputs)
}
Les dossiers tire/, suspension/, powertrain/ et brakes/ sont passés d’un dépôt à l’autre quasiment tels quels. La Magic Formula, les trois zones thermiques, la loi des gaz parfaits et le filtre de relaxation, c’est le même code. Seul le harnais autour a changé, des composants Bevy vers un PhysicsSim que Godot instancie.
79 commits en six jours, du 13 au 18 avril. Config des voitures en ressources Godot, console de dev, réécriture de l’audio, menus, overlays de télémétrie. J’ai aussi dérivé sur autre chose au passage, des ombres de nuages volumétriques et un compute shader qui traverse en ray-marching le volume de nuages de SunshineClouds2 pour en sortir une carte de transmittance projetée au sol.
Le dépôt Bevy, lui, est gelé, et c’est volontaire. Il reste la référence pour les maths, et son fichier de notes le dit noir sur blanc, « frozen, source-of-truth for physics math ». Quand un comportement diverge côté Godot, je compare avec la version Bevy et c’est elle qui a raison. Ce n’est pas un projet abandonné au profit d’un autre. C’est la spec.
Où ça en est
Nulle part depuis avril. Aucun commit d’un côté comme de l’autre.
La raison est précise, et ce n’est pas le manque de temps. Pour aller plus loin, il faut modéliser. Les voitures et les circuits sur lesquels je teste ne sont pas de moi. Or un jeu de course, une fois la physique posée, c’est essentiellement du contenu 3D : des caisses, des roues, des intérieurs, du décor, un tracé cohérent avec ses vibreurs et ses dégagements. Et l’esthétique PSX ne sauve rien de ce côté-là. Un modèle basse résolution qui a de la gueule, c’est une contrainte en plus, pas en moins.
La modélisation 3D, je ne sais pas encore faire. C’est une compétence à part entière, elle se travaille sur des mois, et je n’ai pas envie de bâcler la seule partie qui manque à un projet dont j’aime le reste.
Donc c’est en pause, sans date. La physique tient debout et elle est écrite noir sur blanc dans deux dépôts. Le jour où je saurai sortir une voiture et un bout de circuit corrects, il y aura de quoi les mettre dedans.