Aller au contenu

Simulation N-Corps — Référence Physique

Vue d'ensemble

Le module N-corps (nbody.h / nbody.c) implémente une simulation gravitationnelle temps réel de jusqu'à 16 corps en orbite autour d'une étoile centrale. Le moteur physique privilégie la stabilité long terme (conservation de l'énergie sur des milliers de secondes simulées) en utilisant des techniques éprouvées d'astrophysique numérique :

Composant Technique
Intégrateur Velocity Verlet (symplectique, ordre 2)
Régularisation Adoucissement de Plummer (par paire, adaptatif)
Conditions initiales Formule de vitesse orbitale adoucie
Pas de temps Fixe \(\frac{1}{120}\text{s}\), accumulateur borné

Validé par un test automatisé de 1 200 s avec 0.002 % de dérive d'énergie.


Intégrateur Velocity Verlet

Velocity Verlet (aussi appelé Leapfrog kick-drift-kick) est un intégrateur symplectique : il préserve la structure géométrique des systèmes hamiltoniens, ce qui garantit que l'énergie totale oscille autour de la vraie valeur au lieu de dériver monotoniquement.

Algorithme (par pas fixe \(\Delta t\))

  1. Calculer les accélérations à partir des positions courantes : \(\mathbf{a}_\text{old}\)
  2. Mettre à jour les positions : $\(\mathbf{x} \leftarrow \mathbf{x} + \mathbf{v}\,\Delta t + \tfrac{1}{2}\,\mathbf{a}_\text{old}\,\Delta t^2\)$
  3. Calculer les accélérations à partir des nouvelles positions : \(\mathbf{a}_\text{new}\)
  4. Mettre à jour les vitesses : $\(\mathbf{v} \leftarrow \mathbf{v} + \tfrac{1}{2}(\mathbf{a}_\text{old} + \mathbf{a}_\text{new})\,\Delta t\)$

Implémenté dans integrate_step() dans src/nbody.c.

Pourquoi le Symplectique est Important

Les méthodes non-symplectiques (Euler, RK4) accumulent une dérive séculaire d'énergie proportionnelle au nombre de pas. Sur 1 200 s à 120 Hz, cela représente 144 000 pas — suffisant pour que même de petites erreurs par pas fassent exploser ou effondrer le système. L'erreur bornée d'un intégrateur symplectique maintient les orbites qualitativement correctes indéfiniment.

Références :


Adoucissement de Plummer

Dans un champ gravitationnel pur en \(1/r^2\), deux masses ponctuelles passant très près subissent des forces arbitrairement grandes, menant à des effets de fronde numérique et des échanges chaotiques d'énergie. Le remède standard en astrophysique est l'adoucissement de Plummer :

\[F = \frac{G\,m_i\,m_j}{(r^2 + \varepsilon^2)^{3/2}}\,\hat{\mathbf{r}}\]

La longueur d'adoucissement \(\varepsilon\) régularise la singularité en \(r = 0\), transformant le potentiel newtonien \(1/r\) en le potentiel fini de Plummer \(\Phi = -G\,M / \sqrt{r^2 + \varepsilon^2}\).

Adoucissement Adaptatif par Paire

Plutôt qu'un seul \(\varepsilon\) global, on adapte l'adoucissement aux rayons physiques de chaque paire :

\[\varepsilon^2 = \max\!\bigl(\texttt{NBODY\_SOFTENING\_SQ},\; (\texttt{NBODY\_SOFTENING\_FACTOR} \cdot (r_i + r_j))^2\bigr)\]

Avec les constantes par défaut :

Constante Valeur Rôle
NBODY_SOFTENING_SQ 0.25 Plancher absolu (\(\varepsilon_\min = 0.5\))
NBODY_SOFTENING_FACTOR 2.0 Multiplicateur par paire sur les rayons combinés

Ceci garantit que :

  • Les gros corps (l'étoile, \(r = 1.5\)) bénéficient d'un fort adoucissement (\(\varepsilon \geq 3.6\)).
  • Les petites paires (deux lunes) en reçoivent proportionnellement moins, gardant leur dynamique plus képlérienne.
  • Aucune paire n'atteint jamais la région singulière.

Implémenté dans pair_softening_sq().

Références :


Vitesse Orbitale Adoucie

Une erreur courante est d'initialiser les vitesses d'orbite circulaire avec la formule de Kepler \(v = \sqrt{G\,M/r}\). C'est faux quand l'adoucissement est actif car le potentiel effectif est plus plat que \(1/r\).

La condition correcte d'orbite circulaire pour le potentiel de Plummer est :

\[v = r \cdot \sqrt{\frac{G\,M}{(r^2 + \varepsilon^2)^{3/2}}}\]

Elle vient de l'équilibre entre l'accélération centripète \(v^2/r\) et l'accélération gravitationnelle adoucie \(G\,M\,r / (r^2 + \varepsilon^2)^{3/2}\).

Utiliser la formule non-adoucie surestime la vitesse orbitale, injectant un excès d'énergie cinétique et causant la spirale vers l'extérieur des corps.

Implémenté dans softened_orbital_vel().


Annulation de la Quantité de Mouvement

Après l'initialisation de tous les corps, la quantité de mouvement totale \(\mathbf{p} = \sum m_i \mathbf{v}_i\) est mise à zéro en soustrayant la vitesse du centre de masse de chaque corps :

\[\mathbf{v}_i \leftarrow \mathbf{v}_i - \frac{\sum m_j\,\mathbf{v}_j}{\sum m_j}\]

Cela maintient le centre de masse immobile et empêche le système entier de dériver à travers la scène. L'opération est faite une seule fois à l'initialisation, jamais pendant la simulation (ce qui briserait la symplecticité).


Pas de Temps Fixe avec Accumulateur

Les pas de temps variables détruisent la propriété symplectique. La simulation utilise un \(\Delta t = 1/120\,\text{s}\) fixe avec un motif d'accumulateur standard :

accumulateur += wall_dt × time_scale
borner accumulateur à NBODY_MAX_ACCUMULATOR (1/10 s)
tant que accumulateur >= NBODY_FIXED_DT :
    integrate_step(NBODY_FIXED_DT)
    accumulateur -= NBODY_FIXED_DT

Le bornage maximum de l'accumulateur empêche un emballement de l'intégration après des pics de latence ou lors de la première frame. Le plafond de \(1/10\,\text{s}\) permet de supporter des framerates aussi bas que ~10 FPS sans perdre de temps simulé (jusqu'à 12 pas Verlet par frame pour \(N = 14\) corps — trivial en CPU).


Approches Testées et Rejetées

Au cours du développement, plusieurs techniques alternatives ont été testées et rejetées car elles brisaient l'invariant symplectique ou introduisaient une dissipation d'énergie inacceptable.

1. Collisions Élastiques

Idée : Quand deux corps se chevauchent (\(r < r_i + r_j\)), refléter leurs vitesses avec les formules de collision élastique.

Problème : La détection discrète de collision à pas de temps fixe introduit des impulsions non-réversibles dans le temps. Cela brise la symplecticité et cause une dérive séculaire d'énergie. En pratique, les corps gagnaient de l'énergie à chaque collision, menant à une explosion exponentielle.

Verdict : Supprimé entièrement. L'adoucissement de Plummer empêche le chevauchement sans nécessiter de gestion des collisions.

2. Clamp d'Énergie

Idée : Après chaque pas, mesurer l'énergie totale ; si elle dépasse un seuil, redimensionner toutes les vitesses pour restaurer l'énergie cible.

Problème : C'est une étape de projection qui ne dérive pas d'un hamiltonien. Elle détruit la structure symplectique et introduit un amortissement ou un pompage artificiel selon le signe de la correction.

Verdict : Supprimé.

3. Frein d'Échappement

Idée : Si un corps dépasse un seuil de distance, appliquer une force de traînée pour le ramener.

Problème : Force non-conservative → perte séculaire d'énergie → les orbites décroissent et le système s'effondre.

Verdict : Supprimé.

4. Ré-annulation de la Quantité de Mouvement par Pas

Idée : Soustraire la vitesse du centre de masse de tous les corps à chaque pas.

Problème : C'est une projection non-symplectique appliquée à chaque pas. Elle interfère avec la structure de l'espace des phases de l'intégrateur et cause une dérive descendante de l'énergie.

Verdict : La quantité de mouvement est annulée une seule fois à l'initialisation, jamais pendant la simulation.

5. XPBD (Extended Position-Based Dynamics)

Idée : Utiliser le solveur de contraintes XPBD (de la physique de jeux vidéo) pour imposer une distance minimale entre les corps, remplaçant l'adoucissement par une répulsion basée sur des contraintes.

Problème : XPBD est fondamentalement dissipatif par conception — il résout les contraintes en projetant les positions, ce qui ne conserve pas l'énergie. En test, cela a causé 21 % de perte d'énergie sur 300 s, les orbites ont décru rapidement et tous les corps se sont effondrés sur l'étoile.

XPBD est excellent pour la physique de jeux (ragdolls, tissus) où la dissipation est acceptable voire souhaitable, mais il est catastrophique pour les simulations gravitationnelles qui nécessitent la conservation de l'énergie.

Verdict : Rejeté après test. Verlet pur + fort adoucissement de Plummer est l'approche correcte pour la gravité N-corps.

Références :


Potentiel de Confinement

Les corps qui s'éloignent trop de l'étoile centrale sont repoussés par un potentiel de rappel quadratique — un mur souple qui garde la simulation visuellement bornée sans clamper brutalement les positions.

Physique

Pour chaque corps \(i\) à distance \(r_i\) de l'étoile :

\[V_\text{conf}(r) = \begin{cases} 0 & r \le r_\text{max} \\ \tfrac{1}{2} k (r - r_\text{max})^2 & r > r_\text{max} \end{cases}\]

La force résultante est :

\[\mathbf{F}_\text{conf} = -k\,(r - r_\text{max})\,\hat{\mathbf{r}}\]

où \(r_\text{max}\) = NBODY_CONFINEMENT_RADIUS (25 unités) et \(k\) = NBODY_CONFINEMENT_K (5.0).

Réaction de Newton (3ème loi) sur l'étoile centrale pour préserver le centre de masse :

\[\mathbf{F}_\text{étoile} = +k\,(r - r_\text{max})\,\hat{\mathbf{r}} \times \frac{m_i}{m_0}\]

Le potentiel est conservatif, donc l'intégrateur Velocity Verlet reste symplectique dans la zone de confinement.

Constantes

Constante Valeur Rôle
NBODY_CONFINEMENT_RADIUS 25.0 Distance du mur souple depuis l'étoile
NBODY_CONFINEMENT_K 5.0 Raideur du ressort (\(k \cdot dt^2 \approx 0.0003\))

Amortissement Radial Proportionnel

Le potentiel de confinement seul provoque des oscillations des corps autour de la frontière (trajectoires ping-pong). Un amortissement radial proportionnel retire de l'énergie uniquement à la composante radiale sortante, et seulement dans la zone de confinement :

\[\gamma_\text{eff} = \gamma \cdot \frac{r - r_\text{max}}{r_\text{max}}\]

où \(\gamma\) = NBODY_CONFINEMENT_DAMPING (20.0).

  • Radial uniquement : seule la composante \((\mathbf{v} \cdot \hat{\mathbf{r}})\) est amortie ; la vitesse tangentielle est préservée → moment cinétique conservé.
  • Sortant uniquement : l'amortissement s'applique quand \(v_\text{radial} > 0\) (corps s'éloignant de l'étoile).
  • Proportionnel au dépassement : près de la frontière (\(r \approx r_\text{max}\)), l'amortissement est négligeable ; loin au-delà, il est fort. Cela permet au système d'atteindre un équilibre où les orbites ne traversent plus la frontière et la dissipation d'énergie se stabilise.
  • Conservation de la quantité de mouvement : l'impulsion est transférée à l'étoile centrale (\(\Delta v_\text{étoile} = -(m_i/m_0) \cdot \Delta v_i\)).

Indicateur de Stabilité

L'indicateur HUD utilise la dérive d'énergie signée pour distinguer l'amortissement de la divergence :

Couleur État Condition Affichage
Vert Stable $ \text{dérive}
Jaune Damping \(\text{dérive}_\text{signée} < 0\) E: XX% (retenue)
Rouge Divergent \(\text{dérive}_\text{signée} > 0\) drift: X.XX%

En mode Damping, l'affichage montre E: XX% = \(100 \times (1 - |\text{dérive}|)\), représentant combien d'énergie le système possède encore. Cela évite le confusant « drift: 349% » qui surviendrait avec la métrique absolue.


Effets Visuels de Shockwave

Quand un corps percute la frontière de confinement, un anneau d'onde de choc s'étend depuis le point d'impact, fournissant un retour visuel pour la zone de confinement autrement invisible.

Détection d'Impact

Chaque sous-pas d'intégration vérifie si la distance d'un corps à l'étoile dépasse NBODY_CONFINEMENT_RADIUS avec une vitesse radiale sortante positive. Les impacts sont enregistrés par corps dans des slots NBodyImpact — seule la vitesse maximale de tous les sous-pas d'une frame est conservée, dédupliquant naturellement les hits multiples de l'accumulateur à pas fixe (~12 sous-pas/frame).

Rendu

Les ondes de choc sont rendues comme des quads alignés à l'écran en mélange additif (billboard) avec un motif procédural d'anneau en expansion dans le fragment shader :

  • Profil de l'anneau : bords intérieurs/extérieurs smoothstep autour d'un rayon central dépendant du temps
  • Émissif HDR : la couleur de sortie est multipliée par 4× pour interagir avec le bloom
  • Atténuation : intensité = \((1 - \text{progression}^2)\), montée rapide, extinction lente
  • Billboard : le quad fait face à la caméra via les vecteurs right/up extraits de la matrice vue

Jusqu'à SHOCKWAVE_MAX_ACTIVE (8) ondes de choc simultanées ; la plus ancienne est évincée quand le buffer est plein. Durée = SHOCKWAVE_DURATION (1.2 s), rayon max = SHOCKWAVE_MAX_RADIUS (6.0 unités monde).

Position dans le Pipeline

Les ondes de choc sont dessinées dans le FBO HDR, après les traînées N-corps et avant le post-processing. Le bloom amplifie naturellement le halo de l'anneau :

scene_render() → Skybox → Sphères → Traînées NBody → Shockwave VFX → Sondes
                                                       ↓
                                               postprocess_end() → Bloom → ...

Validation par Tests

La suite de tests automatisée (tests/test_nbody_stability.c) vérifie les invariants physiques sur de longues simulations :

Test Ce qu'il vérifie Seuil
test_nbody_single_step_sanity Un pas n'explose pas Positions finies
test_nbody_energy_conservation Énergie bornée après boost initial \(\Delta E / E_0 < 65\%\)
test_nbody_paused_no_change Sim en pause parfaitement gelée Identique bit à bit
test_nbody_survives_dt_spikes Les pics de frame ne déstabilisent pas Tous les corps \(< 50\) unités
test_nbody_long_run_stability Invariants sur 1 200 s Voir ci-dessous
test_nbody_kinetic_energy_positive \(E_k > 0\) après init Positif
test_nbody_energy_drift_api API drift : \(E_0\) stocké, dérive \(\approx 0\) à \(t=0\) dérive \(< 5\%\) après 10 s
test_nbody_zero_gravity \(G = 0\) signifie aucune accélération Vitesse inchangée après 120 pas

Résultats de Stabilité Long Terme (1 200 s à 120 Hz = 144 000 pas)

Métrique Mesuré Seuil
Dérive d'énergie \(\|\Delta E / E_0\|\) 0.0017 % < 5 %
Dérive du centre de masse 0.001 < 0.5
Distance maximale d'un corps 19.6 < 50
Nombre de corps 7 / 7 inchangé

Référence des Constantes

Toutes les constantes sont définies dans include/nbody.h :

Constante Valeur Description
NBODY_MAX_BODIES 16 Nombre maximum de corps
NBODY_DEFAULT_G 1.0 Constante gravitationnelle
NBODY_SOFTENING_SQ 0.25 \(\varepsilon^2\) minimum
NBODY_SOFTENING_FACTOR 2.0 Multiplicateur d'adoucissement par paire
NBODY_FIXED_DT 1/120 s Pas de temps fixe d'intégration
NBODY_MAX_ACCUMULATOR 1/10 s Temps physique accumulé maximum (supporte ~10 FPS)
NBODY_CONFINEMENT_RADIUS 25.0 Distance du mur souple depuis l'étoile
NBODY_CONFINEMENT_K 5.0 Raideur du ressort de confinement
NBODY_CONFINEMENT_DAMPING 20.0 Coefficient de base d'amortissement radial
SHOCKWAVE_MAX_ACTIVE 8 Nombre max d'ondes de choc simultanées
SHOCKWAVE_DURATION 1.2 s Durée de vie d'un anneau
SHOCKWAVE_MAX_RADIUS 6.0 Rayon max d'expansion (unités monde)
SHOCKWAVE_MIN_VELOCITY 0.2 Vitesse min pour déclencher une onde
TRAIL_DURATION_DEFAULT 4.0 s Durée de vie des traînées en secondes (ajustable 0.5–30 s)

Contrôles en Temps Réel

Vitesse de Simulation (time_scale)

La vitesse de simulation est ajustable au clavier :

Touche (US) Touche (AZERTY) Action
. : Doubler la vitesse (max 64×)
, ; Diviser par 2 (min ⅛×)

Le multiplicateur time_scale suit des puissances de 2 pour garantir que 1.0× est toujours atteignable :

\[\text{time\_scale} \in \{0.125,\; 0.25,\; 0.5,\; 1.0,\; 2.0,\; 4.0,\; 8.0,\; 16.0,\; 32.0,\; 64.0\}\]

Une notification overlay affiche la vitesse actuelle à chaque changement.

Inversion Temporelle

La direction du temps peut être inversée à l'exécution :

Touche (US) Touche (AZERTY) Action
Ctrl+Shift+G Ctrl+Shift+G Bascule la direction du temps (avant/arrière)

Ceci exploite une propriété fondamentale de la gravité newtonienne : les équations du mouvement sont invariantes sous \(t \to -t\). Inverser le temps revient à négativer toutes les vitesses — le système retrace sa trajectoire en sens inverse.

Comme Velocity Verlet est à la fois symplectique et réversible dans le temps (\(\Phi_h^{-1} = \Phi_{-h}\)), la simulation inversée suit la même orbite avec une erreur d'énergie bornée. Après \(N\) pas en avant et \(N\) pas en arrière, le système revient à l'état initial (modulo l'arrondi flottant : \(\sim N \cdot \epsilon_{\text{machine}}\) d'erreur de position).

En marche arrière, le HUD affiche un indicateur ⏪ à côté de la lecture d'énergie. Les contrôles de vitesse (, / .) ajustent la magnitude sans affecter la direction temporelle.

Transition Progressive

L'inversion n'est pas instantanée : time_scale converge linéairement vers sa cible au rythme de 3.0 unités/s (NBODY_TIME_SCALE_RATE). À vitesse \(1\times\), la transition complète avant→arrière prend ~0.7 s :

\[+1.0 \xrightarrow{\text{décélération}} 0.0 \xrightarrow{\text{accélération}} -1.0\]

Cela produit un effet naturel « rembobinage VHS » où les corps ralentissent, se figent brièvement, puis accélèrent en sens inverse. Des multiplicateurs plus élevés prennent proportionnellement plus de temps (ex. \(4\times\) → ~2.7 s).

Contrôle de la Gravité (gravity)

La constante gravitationnelle \(G\) est ajustable en temps réel avec le modificateur Shift :

Touche (US) Touche (AZERTY) Action
Shift+. Shift+: Doubler G (max 128)
Shift+, Shift+; Diviser G par 2 (min 0.125, puis OFF)

\(G\) suit des puissances de 2 à partir de la valeur courante :

\[G \in \{0,\; 0.125,\; 0.25,\; 0.5,\; 1.0,\; 2.0,\; \ldots,\; 128.0\}\]
  • \(G = 0\) désactive la gravité : les corps suivent des trajectoires balistiques en ligne droite à leur vitesse courante.
  • Quand \(G\) change, l'énergie de référence \(E_0\) est recalculée pour que l'indicateur de stabilité ne reflète que l'erreur d'intégration numérique, pas le changement intentionnel de paramètre physique.

Affichage HUD

Quand le mode N-corps est actif et l'overlay texte est activé (F1), deux lignes sont affichées :

  1. Énergie & Gravité : Ek: 77.1 J | G: 2.000 — énergie cinétique \(E_k = \sum \tfrac{1}{2} m_i |\mathbf{v}_i|^2\) et valeur courante de \(G\).
  2. Stabilité : indicateur coloré avec métrique contextuelle :
Couleur État Métrique Exemple
Vert Stable dérive% Stability: Stable (drift: 0.12%)
Jaune Damping énergie retenue Stability: Damping (E: 78%)
Rouge Divergent dérive% Stability: Divergent (drift: 6.42%)

La dérive est \(|E(t) - E_0| / |E_0|\). En mode Damping, l'énergie retenue est \(100 \times (1 - \text{dérive})\), montrant combien d'énergie le système possède encore (évite les valeurs de dérive >100% confusantes).

Stabilité de la Longueur des Traînées — Échantillonnage Temporel

La longueur des traînées est contrôlée par un paramètre de durée (trail_duration, défaut 4.0 s, ajustable 0.5–30 s) plutôt que par un nombre fixe de points. Chaque échantillon enregistré porte un timestamp d'une horloge monotone de simulation :

sim_time += wall_dt × |time_scale|
sample_timer += wall_dt × |time_scale|
tant que sample_timer >= TRAIL_SAMPLE_INTERVAL :
    pour chaque corps :
        ring_push(position, sim_time)   ← horodaté
    sample_timer -= TRAIL_SAMPLE_INTERVAL

Le constructeur de ruban calcule ensuite l'âge par point :

\[\text{age} = \frac{\text{sim\_time} - \text{timestamp}}{\text{trail\_duration}}\]

Les points avec \(\text{age} > 1\) sont ignorés. La largeur et l'intensité utilisent ce facteur d'âge au lieu de l'index dans le ring buffer.

Pourquoi c'est important à bas FPS : À 15 FPS, seuls ~15 échantillons/sec sont enregistrés (moins de positions uniques), mais chacun porte le bon timestamp. Le ruban couvre toujours exactement trail_duration secondes de trajectoire, donc la longueur des traînées est identique à 15 et 60 FPS. À time_scale élevé (ex. 8×), 8 échantillons sont émis par frame, préservant la fidélité spatiale.


Zones de Profilage

Le pipeline N-corps est instrumenté avec des zones de profilage CPU à grain fin (visibles dans Tracy ou tout profileur compatible PROFILE_ZONE).

Zones CPU (PROFILE_ZONE — Thread Principal)

Zone Fichier Ce qu'elle mesure
NBody Physics app.c Wrapper top-level (parent de toutes les suivantes)
NBody Verlet scene.c nbody_step() — gravité O(N²) + Velocity Verlet
NBody Trail Sample scene.c trail_renderer_record() — écriture ring buffers
NBody Instance Build scene.c nbody_write_instances() — génération matrices modèle
NBody VBO Upload scene.c instanced_group_update() → glBufferSubData
Trail Ribbon Build trail_renderer.c build_ribbon() × N corps — staging géométrie CPU
Trail VBO Upload trail_renderer.c glBufferSubData — upload buffer GPU
Trail Draw Calls trail_renderer.c glMultiDrawArrays — N strips batchés en 1 appel

Zones GPU (GPU_STAGE_PROFILER — Contexte OpenGL)

Zone Fichier Ce qu'elle mesure
Instanced Render scene.c Draw call des sphères (partagé avec grille matériaux)
NBody Trails scene.c Passe complète de rendu des traînées

Cycle de Vie des Ressources

Lorsque le mode N-corps est basculé sur OFF, le moteur restaure la grille de matériaux originale en appelant scene_init_instancing() une seconde fois. Ce chemin de ré-initialisation doit d'abord libérer chaque ressource allouée par l'appel précédent pour éviter les fuites de buffers GPU et de mémoire CPU :

/* Nettoyage avant ré-init (scene.c — branche N-body OFF) */
trail_renderer_cleanup(&scene->trail_renderer);
#ifdef USE_TRANSPARENT_BILLBOARDS
if (scene->billboard_instances) {
    platform_aligned_free(scene->billboard_instances);
    scene->billboard_instances = NULL;
}
billboard_sorter_cleanup(&scene->billboard_sorter);
#endif
instanced_group_cleanup(&scene->instanced_group);
billboard_group_cleanup(&scene->billboard_group);
scene_init_instancing(scene);

Le nettoyage reproduit ce que fait scene_cleanup() à la fermeture de l'application. Sans cela, chaque bascule ON → OFF fuyait ~27 Ko (4 blocs : instance VBO, billboard VBO, tableau d'instances aligné, trieur de sphères).

Validé avec just test-integration-valgrind-full — 0 bytes definitely lost.


Carte du Code

Fichier Rôle
include/nbody.h API publique, constantes, structures de données
src/nbody.c Implémentation physique (Verlet, adoucissement, preset)
tests/test_nbody_stability.c Suite de tests de stabilité (8 tests)
include/trail_renderer.h Rendu des traînées (rubans visuels derrière les corps)
src/trail_renderer.c Implémentation des traînées en rubans billboard
src/app_input.c Raccourcis vitesse de simulation (, / .) et gravité (variantes Shift)
src/app_ui.c Overlay HUD N-corps (énergie, gravité, stabilité)
src/app_binding.c Enregistrement dans l'overlay d'aide F2
include/shockwave.h API et structures de données des ondes de choc
src/shockwave.c Renderer des ondes de choc (init, emit, update, draw)
shaders/shockwave.vert Vertex shader billboard (quad face caméra)
shaders/shockwave.frag Fragment shader d'anneau procédural en expansion
shaders/trail.vert Vertex shader des traînées
shaders/trail.frag Fragment shader des traînées