Skip to content

v1.9.4

Choose a tag to compare

@github-actions github-actions released this 13 Jul 07:47
· 17 commits to master since this release

v1.9.4 — 13/07/2026

Ajout — images véhicules/circuits manquantes (Kyalami, Brands Hatch GP, Audi R8 LMS GT3 Evo II, Datsun 240Z Standard/Tuned, Porsche 935, Porsche 911 GT2 RS Clubsport Evo, KTM X-Bow GT2/GT4, Golf 8 R)

  • Images fournies converties au format et à la convention de nommage attendus (.webp, slug exact du circuit/véhicule), circuits redimensionnés à 1920px de large comme les images existantes (les sources faisaient jusqu'à 9 Mo), fichiers originaux supprimés après conversion.
  • Bug trouvé au passage : _sync_track_meta() ne backfillait jamais l'image d'un circuit déjà existant en DB, contrairement à _sync_car_meta() qui le fait pour les véhicules — un circuit créé sans image avant l'ajout du fichier correspondant (cas de Kyalami) ne la récupérait donc jamais, même après redémarrage. Corrigé pour que les deux fonctions aient le même comportement.
  • Brands Hatch GP avait déjà une image générique (brands_hatch.webp, fallback partagé avec Brands Hatch Indy) — mis à jour manuellement vers l'image spécifique désormais disponible (brands_hatch_gp.webp), le mécanisme de synchro ne remplaçant jamais une image déjà définie même par une version plus précise.
  • Vérifié en conditions réelles (Playwright) : les 8 véhicules et les 2 circuits affichent bien leur nouvelle image sur les pages Véhicules et Classement.
  • Bug de doublon trouvé en cours de route : Brands Hatch GP apparaissait deux fois dans le classement. Cause : un fichier de config oublié (aceserver/configs/server-1/test-config.json) contenait "brands_hatch" (minuscule/underscore) comme valeur de circuit au lieu de "Brands Hatch" (casse du catalogue officiel) — _sync_track_meta() comparait les circuits par chaîne exacte, donc les deux variantes créaient deux lignes TrackMeta distinctes pour le même circuit réel. Corrigé : la déduplication se fait maintenant sur un slug normalisé (insensible à la casse/ponctuation), le nom du catalogue officiel étant toujours préféré. Doublon existant supprimé en base.
  • Bug de désalignement trouvé en cours de route (page /tracks — signalé par capture d'écran) : la 1re carte de chaque ligne de grille (circuits, véhicules, sessions) était systématiquement plus haute que les suivantes. Cause : une règle CSS générique .card + .card { margin-top: 16px }, prévue pour espacer des cartes empilées verticalement, s'appliquait aussi aux cartes utilisées comme éléments de grille (où l'espacement est déjà géré par gap) — seule la 1re carte de chaque ligne (sans .card précédente dans le DOM) n'avait pas cette marge, la faisant paraître plus grande que ses voisines. Corrigé en neutralisant la marge spécifiquement à l'intérieur d'un conteneur .grid.

Correction — élévation admin du bot ("Aucun mot de passe admin configuré") : bug confirmé, pas une erreur de manipulation

  • Signalé via une issue GitHub : mot de passe admin bien configuré côté serveur, mais le bot refuse de s'élever en admin (config ou page Live) en prétendant qu'aucun mot de passe n'est configuré. Vérifié en conditions réelles — c'est un vrai bug, pas une mauvaise manipulation.
  • Cause racine : _get_active_config_name() (ace_tcp_client.py) était censée lire un marqueur .active_config à la racine de CONFIGS_DIR pour savoir quelle config est réellement en cours — ce marqueur n'était écrit nulle part dans tout le code. La fonction retombait donc systématiquement sur "le premier fichier .json par ordre alphabétique" du dossier CONFIGS_DIR racine (la bibliothèque de configs disponibles, pas le dossier server-{id}/ où la config réellement déployée par serveur est écrite), sans lien avec la session réellement lancée ni avec server_id — les deux serveurs lisaient toujours le même fichier, ignoré de server_id. Si ce fichier "gagnant" par hasard alphabétique n'a pas de mot de passe admin renseigné (ex: un default.json générique), l'élévation échoue avec exactement le message remonté dans l'issue, même si la vraie session active en a un.
  • Corrigé : la config active est maintenant résolue depuis l'état réel du process manager (process_manager._read_state(server_id), la même source que "quelle config tourne" utilisée ailleurs dans le panel) dans le bon sous-dossier server-{id}/. _get_active_config_name()/_read_active_config() prennent maintenant server_id en paramètre.
  • Bug multi-serveur associé trouvé et corrigé au passage : la route /api/live/bot/elevate-admin (bouton d'élévation de la page Live) n'utilisait aucun server_id — elle ciblait donc toujours le serveur 1, quel que soit le serveur réellement sélectionné dans le panel.
  • Vérifié en conditions réelles sur les deux serveurs configurés (requêtes HTTP contre le process en cours, avec le vrai bot TCP connecté) : server_id=1 et server_id=2 résolvent désormais chacun leur propre config déployée (server-1/Practice-spa.json vs server-2/default.json), et l'élévation admin aboutit bien pour le serveur réellement sélectionné (log élévation admin manuelle envoyée (server=1) / (server=2) confirmé pour chacun).

Correction — les commandes admin (kick, to_pit, skip…) cessaient de fonctionner ~60s après l'élévation

  • Après le fix ci-dessus, toujours signalé par le rapporteur de l'issue : les commandes admin envoyées après élévation manuelle ne fonctionnaient plus. Cause : ACE EVO Server déconnecte le bot toutes les ~60s ("software timeout" — ce bot minimal n'envoie aucun keepalive), et _connect_loop ne ré-élève automatiquement en admin à la reconnexion que si ACE_BOT_IS_ADMIN=true — une élévation manuelle (bouton page Live, avec ACE_BOT_IS_ADMIN=false) ne "tenait" donc qu'environ une minute avant de silencieusement redevenir un simple pilote, sans aucune indication dans le panel (le bouton reste affiché comme élevé).
  • Corrigé : un flag manual_admin mémorise qu'une élévation manuelle a été demandée pour ce serveur ; _connect_loop la ré-envoie automatiquement à chaque reconnexion, avec ou sans ACE_BOT_IS_ADMIN.
  • Vérifié en conditions réelles : élévation manuelle à 02:12:24, reconnexion automatique du bot ~55s plus tard (déconnexion "software timeout"), ré-élévation automatique confirmée dans les logs à 02:13:19 (élévation admin envoyée (server=1) [ré-élévation manuelle après reconnexion]) — l'admin reste actif en continu au lieu de retomber après une minute.

Correction — le bot TCP se déconnectait toutes les ~60 secondes ("software timeout")

  • Cause racine identifiée en instrumentant temporairement la réception réseau : après le handshake initial (ConnectToServerHandshakeResponse), ACE EVO Server n'envoie plus rien au bot, et ce client minimal ne renvoie rien non plus tant qu'aucune commande n'est déclenchée — le serveur considère la connexion silencieuse comme morte après ~60s et la coupe (disconnected due to software timeout), le bot se reconnectant aussitôt en boucle. C'est ce cycle qui rendait toute élévation admin ou action éphémère (voir les deux corrections ci-dessus).
  • Corrigé : en l'absence de tout message serveur pendant 20s, le bot renvoie sa requête de connexion d'origine (ClientConnectionRequest, déjà acceptée par le serveur, sans effet de bord visible côté jeu) en guise de heartbeat, avant que le délai de 60s ne soit atteint.
  • Vérifié en conditions réelles sur les deux serveurs : connexion stable et sans aucune reconnexion pendant plusieurs minutes consécutives (auparavant coupée toutes les ~60s), statut connected: true confirmé via requête HTTP contre le process en cours, aucune régression sur le leaderboard live.

Nouveau — historique de tours en direct, indépendant de la fin de session

  • Constat : "Derniers résultats" ne se met à jour qu'à la fin d'une session (fichier résultat écrit par ACE EVO Server). En mode Practice continu (IsCycleEnabled: true, pas de cycle configuré), le jeu ne termine jamais de session — donc plus aucun résultat n'apparaissait, parfois pendant des semaines, alors que les notifications Discord de meilleur temps continuaient d'arriver (mécanisme totalement séparé, basé sur la surveillance TCP en direct).
  • Vérifié en conditions réelles qu'un arrêt manuel du serveur ne résout pas non plus le problème : ACE EVO Server ne répond pas au signal d'arrêt (SIGTERM) envoyé par Docker, qui le tue en SIGKILL après 10s de délai de grâce (Exited (137)) sans jamais écrire de résultat.
  • Solution : chaque tour roulé est maintenant enregistré en direct via le bot TCP déjà connecté en permanence (même mécanisme que les notifs Discord de meilleur temps), sans attendre la fin d'une session. Nouvelle page Mes temps (/pilot/history, accessible depuis "Mes inscriptions" pour tout pilote ayant lié son compte Steam) affichant l'historique complet : temps, voiture, circuit, type de roulage.
  • Pour rester léger dans la durée : au-delà d'un délai configurable (Paramètres → Panel → Langue & Fuseau, LAP_HISTORY_RETENTION_MONTHS, défaut 6 mois), l'historique détaillé n'est pas supprimé mais archivé — regroupé par mois (pilote/circuit/type de roulage), avec le détail temps + voiture de chaque tour conservé, seul l'horodatage précis étant perdu au profit d'un résumé mensuel compact (meilleur temps, moyenne, nombre de tours).
  • Nouvelles tables lap_record (détail récent) et lap_archive (résumés mensuels compactés), créées automatiquement au démarrage — aucune action requise pour les installations existantes.

Correction — le toggle "Auto-restart" du widget statut (page Serveur) ne faisait rien

  • toggleAutoRestart() (app.js) commençait par chercher l'élément #chk-auto-restart (la case à cocher de la barre de contrôle) et s'arrêtait immédiatement si absent — hors ce checkbox n'existe pas dans le widget "Auto-restart" du tableau de bord (#srv-auto-restart-card), donc le cocher/décocher depuis la page statut n'envoyait jamais la requête à l'API et ne changeait rien en réalité, silencieusement. Le contournement (passer par la configuration) fonctionnait car cette page contient bien #chk-auto-restart.
  • Corrigé : la fonction gère maintenant les deux emplacements possibles du toggle, sans dépendre de la présence de l'un ou l'autre, et synchronise les deux affichages (case à cocher + libellé) immédiatement après une bascule réussie, sans attendre le prochain rafraîchissement périodique.
  • Vérifié en conditions réelles (Playwright, sur le vrai ace-server) : avant le fix, aucune requête réseau n'était envoyée en cliquant le toggle du widget statut et l'état revenait à sa valeur d'origine après rechargement de la page ; après le fix, la valeur est bien persistée côté serveur et survit à un rechargement.

Correction — gap i18n préexistant : plusieurs chaînes lazy_gettext jamais traduites

  • La commande d'extraction i18n du projet n'incluait pas le mot-clé _l (lazy_gettext), utilisé pour toutes les descriptions de réglages dans admin.py construites au chargement du module. Résultat : une partie de ces descriptions (ports serveur, dossiers ACE EVO, labels Actif/Inactif, messages Discord configurables, etc. — 86 chaînes) n'avaient jamais été captées pour traduction et retombaient silencieusement sur le français pour les utilisateurs non-francophones, indépendamment de cette version.
  • Corrigé : ré-extraction complète avec le bon mot-clé, traductions ajoutées dans les 5 langues (fr/en/de/es/it). 0 clé vide ou approximative restante dans les 5 catalogues (86 chacun, contre 92 à 97 selon la langue avant correctif).

Accès admin/superadmin à l'historique de tours + lien navbar

  • /pilot/history était réservée aux comptes pilote (is_pilot), alors que les comptes admin/superadmin ont eux aussi un steam_id lié (même flux Steam OpenID) et peuvent donc avoir des tours enregistrés — ouverte aux deux, avec un lien "Mes temps" ajouté sur /mon-compte (équivalent admin de "Mes inscriptions").
  • Lien "Mes temps" ajouté dans la barre de navigation (à côté de "Live"), visible pour tout compte connecté ayant un steam_id lié, pilote ou admin.

Refonte — vue "Classement" de la page Résultats : grille par circuit + page détail filtrable

  • L'ancien classement (/results?v=leaderboard) affichait une liste accordéon d'une carte par circuit ayant déjà des résultats — les circuits jamais roulés n'apparaissaient pas, et il fallait déplier chaque carte pour voir le meilleur temps.
  • Remplacé par une grille de cartes photo compactes (une par circuit configuré dans le jeu, tous circuits confondus — 37 actuellement), affichant un encadré "TOP 1" avec le meilleur temps directement sur l'image, ou "Aucun résultat" si le circuit n'a jamais été roulé. Circuits avec un temps triés en premier (ordre alphabétique), puis circuits sans résultat (ordre alphabétique) — jamais mélangés par ordre alphabétique pur. Un clic ouvre une page dédiée par circuit (/results/circuit/<id>) avec le détail complet et des filtres combinables (catégorie de véhicule, véhicule précis, plage de PI), mis à jour sans rechargement de page (HTMX).
  • Rapprochement TrackMeta ↔ résultats vérifié fiable par égalité exacte sur (nom de circuit, layout) — les deux sources utilisent la même convention de nom verbeuse (confirmé en comparant un fichier résultat réel et la config active).
  • Nettoyage : suppression du CSS et du JS d'accordéon devenus inutiles (.lb-grid, .lb-card, .lb-detail, etc. — ~120 lignes mortes), les badges catégorie (Road/Race/Track) réutilisent désormais .veh-badge-* (déjà utilisé ailleurs) au lieu de classes .lb-pi-s/a/b/c/d/x qui ne correspondaient en réalité à aucune donnée existante (bug d'affichage préexistant, silencieux — le badge PI ne prenait jamais sa couleur).
  • Colonnes Secteur 1 / 2 / 3 ajoutées au tableau de la page détail circuit — la donnée existait déjà dans results_parser.py (best_splits, calculée pour chaque pilote sur son tour de référence : le meilleur tour en Practice/Qualify, le tour le plus rapide en course) mais n'était jusqu'ici exploitée que sur la page détail d'une session individuelle, pas sur le classement par circuit.
  • Corrigé au passage : les filtres (catégorie, véhicule, PI min/max) de la page détail circuit s'étiraient sur toute la largeur au lieu de garder une taille raisonnable — .form-control impose width:100% en CSS, qui l'emportait sur les classes Tailwind w-* à spécificité égale (chargées avant dans la feuille de style). Corrigé en enveloppant chaque champ dans un conteneur de largeur fixe.

Refonte — onglet "Résultats" de la page Résultats : même style de grille compacte

  • Les cartes de session (dans chaque groupe/événement, structure de regroupement conservée) reprennent le style compact de la nouvelle grille Classement : image de circuit en vignette, encadré "1er · pilote + temps" en overlay, au lieu de l'ancienne carte plus grande avec fond d'image et texte empilé.
  • Ordre inchangé (le plus récent en premier — received_at décroissant), comme demandé.

Correction — menu mobile (hamburger) totalement inopérant

  • Le bouton hamburger (@click="$dispatch('toggle-mobile-nav')") n'était rattaché à aucun composant Alpine.js (x-data) ancestor — Alpine n'initialise ses directives qu'à l'intérieur d'un arbre x-data, donc @click n'était jamais lié : clic sans aucun effet, silencieusement, en vue mobile uniquement (le menu desktop ne dépend pas de ce bouton). Corrigé en ajoutant x-data directement sur le bouton. Vérifié en conditions réelles (Playwright, viewport 390px) : le menu s'ouvre désormais correctement.
  • Liens du menu mobile agrandis (py-3/text-base au lieu de py-2/text-sm) pour des cibles tactiles plus confortables. Une refonte plein écran plus ambitieuse a été tentée puis abandonnée : elle causait une page blanche/noire sur téléphone réel (non reproduit en émulation) — reste sur la structure dropdown existante, volontairement inchangée par prudence.

Correction — gap i18n préexistant : app/templates/_partials/ jamais scanné par l'extraction

  • Découvert en travaillant sur les traductions ci-dessus : la commande d'extraction pybabel ignore par défaut tout dossier commençant par _les 8 fichiers de app/templates/_partials/ (grille véhicules, listes pilotes en attente/approuvés, lignes serveur/événement, toasts…) n'ont donc jamais été scannés pour traduction, quelle que soit la version. ~23 chaînes concernées, toujours retombées silencieusement sur le français pour les utilisateurs non-francophones.
  • Corrigé : ajout du flag --ignore-dirs='.git' (qui écrase la valeur par défaut excluant les dossiers _*) à la commande d'extraction, traductions ajoutées dans les 5 langues. Commande documentée dans CLAUDE.md pour ne pas reproduire l'oubli.