toolsdebuggingaiworkflowdxtopology

Comment on a outillé une IA pour déboguer un moteur de génération procédurale

Un changement anodin peut casser silencieusement un monde entier. On a rendu chaque couche du moteur inspectable, diffable et déterministe, pour que l'IA puisse valider nos changements seed par seed — sans nous.

NeToNuH Team12 min read

TL;DR

On développe NeToNuH, un jeu de gestion avec un moteur de monde procédural en Rust/Bevy/WASM. Debugger ce moteur à la main est long, répétitif, et faillible. On a donc construit avec l'IA une suite d'outils — CLI déterministe, baselines JSON/SVG/PNG, invariants, diffs, hooks — qui permet à l'IA de valider chaque changement automatiquement, seed par seed, en quelques secondes. On n'a pas "mis de l'IA partout". On a rendu le système inspectable pour que l'IA puisse bosser proprement dessus.


Dans cet article : Le problème · Le principe · La boucle · Un cas concret · Les outils · Les limites · Conclusion


Le problème

Imaginez. Vous modifiez l'algorithme de génération des rivières. Vous relancez le jeu. À l'oeil nu, tout a l'air normal. Deux semaines plus tard, vous découvrez que les biomes désertiques ont disparu du seed 9999.

C'est le quotidien d'un moteur de monde procédural. Un changement anodin à l'étape 2 peut casser silencieusement l'étape 7.

On pourrait tout tester à la main. Relancer 30 seeds, inspecter chaque couche visuellement, comparer des heatmaps. Des gens le font. Mais ça prend un temps fou, et c'est le genre de tâche répétitive qu'un humain finit par bâcler.

Alors on a pris une autre approche. On a construit des outils avec l'IA, pour l'IA. Des outils qui transforment "j'espère que j'ai rien cassé" en "l'IA me confirme que tout est bon, seed par seed, couche par couche".


Règle n°1 : tout doit être inspectable

Avant de parler des outils, il faut parler du principe qui les rend possibles.

Un système débogable par une IA, c'est un système qui peut répondre à deux questions :

  1. Quel est l'état actuel ? — de façon complète, structurée, lisible par une machine
  2. Est-ce que cet état est correct ? — par rapport à des règles connues et des snapshots antérieurs

Ça paraît évident. Mais dans la plupart des codebases, l'état du système est dispersé entre des logs texte, des variables en mémoire, et l'intuition du dev qui a écrit le code. Une IA ne peut pas travailler avec ça.

Notre règle est simple : chaque sous-système doit pouvoir cracher son état complet en JSON, et cet état doit être déterministe à seed égal.

Ça veut dire :

  • Tout prend un --seed en paramètre
  • Les sorties sont en JSON (lisible par n'importe quel programme) et en PNG/SVG (compréhensible par l'humain, et par certains modèles d'IA)
  • Chaque seed canonique a un baseline — un snapshot de référence qu'on stocke dans le repo
  • Toute modification du code peut être validée en une commande : régénérer, diff les baselines

[IMAGE: diagramme du principe d'inspectabilité — IA ↔ CLI ↔ Baselines ↔ Diff]


Le feedback loop

Une fois ce principe en place, voilà à quoi ressemble une session de debug typique :

  1. Je dis à l'IA : "Les rivières coulent vers le nord sur le seed 42, c'est pas normal"
  2. L'IA modifie l'algo de flux dans packages/topology/
  3. Un hook automatique lance cargo fmt + cargo check
  4. L'IA lance debug:validate --seeds 42,123,9999
  5. L'IA régénère les baselines et les compare via debug:diff
  6. L'IA me dit : "Seed 42 : flow direction corrigée. Seeds 123 et 9999 : pas de régression. Voilà ce qui a changé dans le graphe hydrologique."

Zéro intervention humaine dans la boucle de validation. Mon seul job : donner l'intention initiale, lire le rapport final.

[IMAGE: diagramme du feedback loop — Prompt → Edit → Auto-check → Validate → Diff baselines → Report]

Et le plus important : chaque bug trouvé enrichit le système. Un invariant qui n'existait pas est ajouté à validate. Un nouveau baseline est goldé. La prochaine fois, ce bug sera détecté automatiquement avant même que je le remarque.


Un cas concret

Pour rendre ça tangible, voici un exemple réel de ce que cette boucle peut attraper — et comment.

Le changement

On modifie la fonction de bruit qui contrôle l'érosion des rivières. On ajuste un paramètre pour rendre les vallées fluviales plus larges. Le changement tient en 3 lignes.

On build, on lance le jeu sur le seed 42. Visuellement, les rivières sont effectivement plus larges. Tout a l'air parfait.

Ce que l'IA détecte

Avant de commit, l'IA lance la boucle de validation :

cargo run --features cli -- debug:validate --seeds 42,123,9999

Résultat :

Seed 42    ✓ determinism    ✓ NaN/Inf    ✓ elevation bounds    ✓ land coverage (38.2%)
           ✓ flow bounds    ✓ river termination    ✓ tectonic determinism
           ✗ meso stamps — pond detected on ocean tile at (1248, 3412)

Seed 123   ✓ determinism    ✓ NaN/Inf    ✓ elevation bounds    ✓ land coverage (41.7%)
           ✗ river length p95 — 847m → 1203m (+42%)

Seed 9999  ✓ determinism    ✓ NaN/Inf    ✓ elevation bounds    ✓ land coverage (35.9%)
           ✓ all checks passed

1/3 seeds failed — meso stamp violation on seed 42, river length regression on seed 123

Deux problèmes qu'aucun humain n'aurait repérés à l'oeil nu :

  • Un étang s'est formé sur une case océan dans le seed 42
  • La longueur des rivières a explosé de 42% sur le seed 123

Le diagnostic

L'IA creuse avec debug:sample sur le point problématique :

cargo run --features cli -- debug:sample --x 1248 --z 3412 --format json
{
  "position": { "x": 1248, "z": 3412 },
  "final_height": -0.03,
  "breakdown": {
    "tectonic": { "continentalness": 0.12 },
    "continent": { "base_elevation": -0.08, "category": "ocean" },
    "meso":     { "stamp_type": "pond", "height_delta": 0.05 }
  }
}

Le meso stamp "pond" s'est posé sur une case classée "ocean" par la couche continent. Normalement, une règle d'exclusion empêche ça — mais l'élargissement des rivières a modifié le masque de clearance, et le stamp est passé à travers.

Le fix

L'IA ajuste la règle d'exclusion dans la couche meso pour prendre en compte la nouvelle largeur des rivières. Elle relance la boucle :

3/3 seeds passed. All invariants green.
  river length p95: 847m (baseline: 842m, within tolerance)
  meso stamps: 0 violations

Temps total : 3 minutes. Sans intervention humaine au-delà de la demande initiale.


Les quatre piliers

Derrière cette boucle, il y a quatre familles d'outils. En voici l'essentiel.

1. Topology Debug CLI

Un binaire Rust avec 25+ sous-commandes, feature-gated derrière --features cli. Chaque commande inspecte une couche spécifique du pipeline de génération du monde.

La commande centrale est validate — 1475 lignes qui vérifient 20+ invariants en quelques secondes : bornes d'élévation, NaN/Inf, terminaison des rivières, couverture terrestre, déterminisme bit-à-bit. Si un invariant casse, l'IA sait exactement lequel, sur quel seed.

Pour creuser, sample descend à travers toutes les couches pour un point donné et décompose le résultat final en JSON. diff produit un comparatif structuré (pixel-perfect pour les PNGs + JSON avec deltas numériques). metrics dump un snapshot numérique complet — distribution des biomes, percentiles de rivières, temps de génération.

# Check-up complet
cargo run --features cli -- debug:validate --seeds 42,123,9999

# Inspection d'un point
cargo run --features cli -- debug:sample --x 2048 --z 4096 --format json

# Diff entre deux versions
cargo run --features cli -- debug:diff --baseline A --candidate B

2. Baselines

Les baselines sont la mémoire du monde. Dans packages/topology/baselines/, on stocke pour chaque seed canonique :

  • graph-first/ : 7 artifacts structuraux — le graphe monde complet (JSON), le graphe hydrologique (JSON + SVG), les polygones de continents et régions (SVG), le rapport de validation, le snapshot de métriques
  • visual-debug/ : 24 PNGs — une par mode de rendu de chaque couche (tectonique, continent, régions, meso)

[IMAGE: continent_minimap.png — la minimap classique, vue d'ensemble du monde] [IMAGE: hydrology_graph.svg — le graphe des rivières, chaque edge = un segment de flux] [IMAGE: continent_regions.png — les biomes, chaque couleur = un biome]

Tout se régénère en une commande :

cargo run --features cli -- debug:graph-baseline --seeds 42,123,9999 --output baselines/graph-first

Un git diff sur les baselines vous dit exactement ce que votre changement a modifié dans le monde — pas dans le code, dans le résultat. Bonus : un validateur croisé dans packages/core/ rebuild le graphe depuis le runtime du jeu avec des checksums FNV-1a, pour détecter les divergences entre les packages.

3. Perf Monitor

La performance est une autre dimension critique, surtout en WASM. On a construit apps/perf-monitor/ : un serveur Bun qui reçoit les métriques du jeu en POST et les broadcast en SSE à un dashboard temps réel (FPS, frame times, chunks, distribution LOD).

[IMAGE: capture du dashboard perf-monitor — FPS chart + métriques temps réel]

Les métriques sont loggées en JSONL. L'IA peut lire ce fichier, détecter une régression de frame time, et la corréler avec un commit — sans lancer le jeu visuellement.

4. Skills & Hooks

Les outils savent quoi faire. Les skills et hooks savent quand le faire.

Skills (.claude/skills/) : 9 playbooks markdown que l'IA charge avant de travailler sur un domaine. Chaque skill encode les invariants à vérifier, les commandes à lancer, les pièges connus, et les règles de décision. Par exemple, topology-debug/SKILL.md :

Avant de claim qu'un fix est bon, run debug:validate --seeds 42,123,9999 et compare aux baselines. Si un seed casse, le fix n'est pas bon, point.

Hooks (.claude/hooks/) : 5 scripts shell automatiques. check-game.sh lance cargo fmt + cargo check après chaque édition de fichier Rust. protect-project-files.sh bloque les modifications de .env, dist/, target/. stop-skill-summary.sh rappelle à l'IA ce qu'elle doit vérifier en fin de session.

Résultat : l'IA ne peut pas oublier une validation. Elle ne peut pas éditer un fichier protégé. Elle suit un protocole, pas une intuition.

Tableau récapitulatif

PilierQuand l'IA l'utiliseSortie cléCe que ça valide
Debug CLIAprès chaque changement de codeJSON structuré, logs texte20+ invariants (bornes, NaN, déterminisme, terminaison...)
BaselinesAvant/Pendant/Après un changementJSON, SVG, PNGRégression structurelle et visuelle seed par seed
Perf MonitorPendant l'exécution du jeuJSONL temps réelFPS, frame times, chunks, distribution LOD
SkillsAu début de chaque tâcheProtocole de validationAdéquation du process de vérification au domaine
HooksÀ chaque action d'éditionCommands shell autoFormat, compilation, protection des fichiers

Les briques externes

On n'a pas tout construit. On a intégré des outils existants que l'IA sait invoquer :

  • code-review-graph — graphe de connaissance du code. L'IA peut demander "qu'est-ce qui appelle cette fonction ?" ou "quel est l'impact de ce changement ?" sans scanner les fichiers.
  • Chrome DevTools MCP — screenshots du jeu, logs console, évaluation JS dans le navigateur. Indispensable pour le debug WASM.
  • CI/CD GitHub Actionscargo fmt --check, cargo clippy -- -D warnings, build WASM/web/docs en parallèle.
  • Criterion.rs — micro-benchmarking statistique avec détection de régressions.

On les a pas écrits, mais on les a câblés dans le workflow.


Ce que ça ne remplace pas

Soyons honnêtes : ce système a des angles morts.

Le game feel. Les invariants peuvent tous être verts, et le monde peut quand même être moche. Une rivière "valide" peut être esthétiquement ratée. Le debug CLI vérifie la correction, pas la beauté. Ça reste du ressort du playtest humain.

Les seeds non canoniques. On vérifie 3 seeds (42, 123, 9999). C'est un échantillon représentatif mais pas exhaustif. Un bug qui n'apparaît que sur le seed 777 ne sera pas détecté par la boucle automatique. On a choisi ces seeds parce qu'elles couvrent des configurations variées (terre 35-42%, topologies différentes), mais c'est un trade-off entre couverture et temps d'exécution.

Les bugs visuels subtils. Un artefact de rendu qui n'affecte pas les données sous-jacentes passe sous le radar. Les PNGs de debug aident, mais ils ne couvrent pas tous les angles de caméra ni tous les états du moteur de rendu.

L'intention de design. L'IA peut te dire qu'un invariant est cassé. Elle ne peut pas te dire si le monde est intéressant. La direction créative reste humaine.

Ceci dit, chaque bug qu'on trouve élargit le filet : on ajoute un invariant, un baseline, ou une règle dans un skill. Le système apprend de ses angles morts.


Ce que ça change concrètement

Avant cette infrastructure, modifier la génération du monde c'était :

"J'ai touché la topo, j'espère que j'ai rien pété. On verra plus tard si quelqu'un tombe sur un biome buggé."

Maintenant c'est :

"L'IA a validé 3 seeds, 20+ invariants, 7 artifacts par seed, et le diff est clean. On peut merge."

Le temps de confiance est passé de heures à secondes.

Et ça ne concerne pas que la topo. Le perf monitor détecte les régressions de frame time. Les skills encodent les règles métier de chaque domaine. Les hooks empêchent les erreurs de débutant. Le graphe de code donne à l'IA une vue d'ensemble qu'aucun humain n'a en tête.

On n'a pas "mis de l'IA" dans notre processus de debug. On a rendu le processus de debug lisible par une IA. C'est une nuance qui change tout.


Si vous voulez suivre la suite — comment on applique ces principes à l'architecture du jeu elle-même — lisez notre article sur le Domain-Driven Design dans un projet Bevy. Ou si vous débarquez, commencez par l'annonce du projet.

Si ce genre d'approche vous parle, on partage régulièrement ce qu'on apprend sur le process et les outils. Le repo est public, les baselines sont dans le repo, et les skills aussi. Venez voir.


La NeToNuH Team