← Retour au Portfolio
📖 Plongée Technique

Simulation & Algorithmes

Flocking multi-agent, champs de potentiel, écosystème auto-régulé, génétique paramétrique — la plongée technique complète.

Étude Technique N°3
🧠
Les algorithmes qui font penser les poissons

L'architecture est en place. Maintenant, la question clé : comment 100 000 agents prennent-ils des décisions individuelles chaque frame ? Cette étude explore les algorithmes de simulation : flocking, partitionnement spatial, et les 15 forces qui gouvernent chaque poisson.

Algorithmes de Simulation Multi-Agent & Modélisation d'Écosystème

Comment se comportent 100 000 poissons ? Cette étude explique les algorithmes qui donnent vie au banc : trouver ses voisins, nager ensemble, fuir les prédateurs et interagir avec l'environnement.

Architecture multi-agent complète avec grille spatiale compressée, forces Reynolds étendues, champ de potentiel avec diffusion, et pipeline en phases découplées. Profilé et mesuré empiriquement.

15
Forces de Navigation
Séparation, alignement, cohésion…
RapideO(N)
Complexité Linéaire
Grille spatiale CSR
8
Phases de Pipeline
Exécution séquentielle stricte
3ms
Budget Algorithmes
Pour 100K agents

🗺️ 2.1 Partitionnement Spatial : Grille Uniforme CSR + Quadtree Adaptatif

⚙️ Le problème : 14 milliards de comparaisons

Le problème majeur : chaque poisson doit connaître ses voisins proches. L'approche naïve compare chacun avec tous les autres — soit 14 milliards de comparaisons pour 100K poissons. Intenable.

La solution : découper le monde en une grille spatiale. Chaque poisson est rangé dans la case correspondant à sa position. Pour trouver ses voisins, il suffit de regarder les 9 cases adjacentes. Le temps de calcul passe de 10 minutes à 3 millisecondes — pour le même résultat. En entreprise, c'est le même principe que les index de base de données : au lieu de chercher partout, on va directement au bon endroit.

🧪 Le format CSR et pourquoi c'est rapide

Le format CSR (Compressed Sparse Row) est standard dans les bases de données columnar (ClickHouse, Apache Arrow). Le principe : au lieu de listes de listes, on utilise 2 tableaux contigus — un pour les données, un pour les offsets. Résultat : parcours séquentiel = cache-friendly.

Le tri par radix (en O(N) !) remplace le sort natif de JS (O(N log N)) et fonctionne sans allocations. C'est le genre d'optimisation qui fait la différence entre 60 FPS et 30 FPS à grande échelle.

Complexité naïve : O(N²) = O(100000²) = O(1.44 × 10¹⁰) par frame
Avec grille spatiale : O(N × K) où K = boids/cell ≈ 8-15
Réduction : ~10⁹× plus rapide
Équation 4 — Réduction de Complexité par Hashing Spatial
Algorithme

Grille Uniforme CSR

Monde divisé en cellules de taille fixe. 6 indices indépendants (boids, food, predators, obstacles, refuges, seaweed) avec dirty-cell tracking. Prefix sums O(dirty) au lieu de O(totalCells). Le seuil 30% décide entre scan linéaire et sweep trié.

Algorithme

Quadtree Sparse Adaptatif

Subdivision dynamique : seules les régions peuplées existent. Depth max 5 (cellules de 625px à monde 20Kpx). Near viewport: depth forcée 4. Far: depth 1 (macro cells 10Kpx). SoA NodePool avec free-list, zéro allocation.

// Lifecycle per frame (SpatialGrid):
1. clear()           — reset only dirty cells O(dirty)
2. insertIdx()       — accumulate stats + cache boidCell[i]
3. buildCellIndex()  — prefix sums + fill flat CSR buffer
4. finalizeCellStats() — coherence, cluster/individual decision
5. query*()          — spatial lookups during flocking

// Per-race stats enable race-aware flocking (same O):
cellRaceVxSum[ci * NUM_RACES + raceIdx] += vx;

🐟 3.1 Comportement de Banc en Détail3.1 Modèle de Flocking Étendu (Reynolds++)

⚙️ 15 règles simples pour un comportement réaliste

Chaque poisson suit 15 règles comportementales : ne pas se rentrer dedans, suivre la direction du groupe, rester groupé, fuir les prédateurs, chercher de la nourriture... Individuellement, ces règles sont simples. Mais combinées sur 100 000 poissons, elles produisent des bancs réalistes qui se forment, éclatent et se reforment — exactement comme dans la nature.

Chaque force est pondérée et les résultats sont plafonnés pour éviter les comportements aberrants. Le tout tourne en moins de 3ms pour 10 000 boids.

🧪 Le modèle Reynolds en production

Craig Reynolds (1987) a défini 3 forces : séparation, alignement, cohésion. En production, il en faut beaucoup plus. Ici on en utilise 15 : les 3 de base + fuite prédateur, attraction nourriture, courant marin, frontière monde, etc.

La difficulté : les poids relatifs. La fuite prédateur doit toujours gagner sur la cohésion, sinon les poissons nagent vers le requin. J'ai passé des heures à tuner ces poids — c'est plus de l'artisanat que de la science.

F⃗total = ws·F⃗sep + wa·F⃗align + wc·F⃗coh + wf·F⃗flee + F⃗food + F⃗obstacle + F⃗current + F⃗vortex + F⃗draft + F⃗mate + F⃗edge + F⃗temperature + F⃗potential + F⃗formation + ...
Équation 5 — Superposition des Forces du Modèle Étendu
Force Formule Complexité Skip Rate
Séparation Σ (pos - voisin) / dist² O(K)
Alignement avg(v⃗voisins) - v⃗self O(K) ou O(1) cluster
Cohésion centroid(voisins) - pos O(K) ou O(1) cluster
Fuite prédateur -Σ dir(pred) × (1/dist) O(P) reactionN
Recherche nourriture dir(foodnearest) × hunger O(F/cell) reactionN
Draft (aspiration) dir(leader) si cos(angle) > θ O(K)
Courant marin bilinear_sample(flowField) O(1) perfFlowSkip
Champ potentiel ∇Φ(x,y) × tierWeight O(1)
Formation Modèle configurable par race O(1)

💡 Optimisation : Frame Staggering Biologique

Les poissons ne vérifient prédateurs et nourriture que 1 frame sur N (reactionN = 2-12 selon population). Ce n'est pas un hack — c'est biologiquement réaliste ! Les vrais poissons ont un temps de réaction de ~50-200ms. Gain : -66% de scans par frame.

🐦

Inspiré par la nature

Ce modèle a été inventé en 1987 par Craig Reynolds. Il est utilisé dans les films (Le Monde de Nemo, Le Seigneur des Anneaux) pour animer des foules réalistes. Ici, on l'a étendu avec 15 comportements différents.

🧲 3.2 Guidage Intelligent des Poissons3.2 Champ de Potentiel pour Navigation O(1)

⚙️ Une carte invisible qui guide les poissons

Comment guider 100 000 poissons sans que chacun doive scanner tout le monde ? Une carte invisible indique les zones de nourriture et de danger. Chaque poisson lit simplement la pente locale pour savoir où aller — comme suivre la pente d'une colline.

Chaque poisson lit le gradient (la pente) du champ à sa position pour savoir où aller. C'est comme suivre une pente : on descend vers la nourriture, on monte pour fuir. Cette technique réduit la navigation à un calcul O(1) par poisson, au lieu de parcourir tous les points d'intérêt du monde.

🧪 Le gradient en pratique

Le "gradient" ici est simple : on regarde la valeur à gauche et à droite d'un poisson sur la grille. Si la valeur est plus grande à droite, le poisson va à droite. C'est une différence finie, pas besoin de maths avancées.

L'important en production : le gradient peut avoir des discontinuités aux bords de la grille. Il faut les gérer proprement (conditions de bord), sinon les poissons se coincent dans les coins. Bug classique que j'ai vu dans 3 projets différents.

Φ(x,y) = Σattract wi/dist² − Σrepulse wj/dist²
∇Φ = (Φ(x+1,y)−Φ(x−1,y), Φ(x,y+1)−Φ(x,y−1)) / 2
F⃗nav = α·∇Φattract − β·∇Φrepulse
Équation 6 — Champ de Potentiel et Gradient de Navigation
Performance

Sparse Clear

Seules les cellules modifiées (dirty tracking) sont réinitialisées chaque frame, pas les N×M cellules.

Algorithme

Diffusion Throttled

Box blur 3×3 sur les couches d'attraction/répulsion, exécuté 1 frame sur 4 pour lisser les gradients.

Algorithme

Intégrale Temporelle

Accumulation exponentielle : les boids Tier 2 lisent un gradient moyenné dans le temps, plus stable.

Une carte GPS invisible pour les poissons 🗺️

Chaque poisson a un "GPS" invisible qui lui dit : "la nourriture est par là" et "le danger est par ici". Pas besoin de voir les autres poissons — la carte suffit. C'est comme suivre les odeurs dans l'eau.

💼 Même principe que le guidage d'une app

Ce système de navigation est identique à ce qu'utilise un robot aspirateur pour éviter les obstacles, ou une app GPS pour trouver le chemin le plus court. Le principe de "champ de potentiel" est un standard de la robotique.

🧬 3.3 Génétique Paramétrique & Spéciation Dynamique

Chaque espèce possède un génome de 28 gènes continus [0,1] encodant la morphologie (corps, nageoires, tentacules) et les patterns visuels. La reproduction inter-espèces déclenche un algorithme de spéciation avec crossover, mutation et détection de similarité.

Gchild[i] = select(GA[i], GB[i], random) + N(0, σmutation)
distance(GA, GB) = √(Σ(GA[i] − GB[i])²)
Si similarity(candidate, existing) > 0.85 → absorption (pas de nouvelle espèce)
Équation 7 — Crossover Uniforme et Distance Génomique
Catégorie Gènes (indices) Exemples
Morphologie Corps 0-14 bodyElongation, headPointiness, tailForkDepth, dorsalHeight, jawProminence
Appendices 15-22 finRayLength, tentacleCount, shellCoverage, clawSize, antennaeLength
Patterns Visuels 23-27 patternType (stripes/spots/rings/…), patternScale, patternContrast, accentHue

🧬 Résultat : 10 000+ Noms Uniques

Le système de nommage combine préfixes taxonomiques (Neo-, Proto-, Archi-), suffixes latins (-oid, -ella, -opsis), et combinaisons uniques curées (Requin+Poulpe = "Krakenodon"). Slot recycling des espèces éteintes.

L'ADN des poissons virtuels 🧬

Chaque espèce a son propre "code génétique" : 28 caractéristiques numériques qui définissent sa taille, sa couleur, sa vitesse, la forme de sa queue… Quand deux poissons se reproduisent, le bébé hérite d'un mélange des deux parents — avec parfois des mutations surprises !

�� 3.4 Simulation de Fluides : Flow Field & Température

⚙️ Courants marins et température

Le monde possède deux champs superposés : un champ de vélocité (courants marins qui poussent les poissons) et un champ de température (zones chaudes près des rifts, froides en surface). Chaque espèce a une température optimale et cherche naturellement à y rester.

Ces champs se propagent par diffusion. Les poissons lisent simplement la valeur à leur position et ajustent leur trajectoire. La grille fait 64×36 cases, assez précise pour guider sans coûter cher.

🧪 La diffusion en pratique

La diffusion (box blur) est une technique standard en traitement d'image. Le même filtre 3×3 est utilisé dans Photoshop pour le flou gaussien. Ici, on l'applique à la grille de potentiel pour que les signaux se propagent naturellement.

Piège classique : appliquer le blur in-place modifie les données pendant qu'on les lit → résultat incorrect. Il faut un double buffer (lire de A, écrire dans B, puis swap). C'est le même pattern qu'en rendu GPU (double buffering).

Algorithme

Gyres Atmosphériques

Tourbillons permanents avec position, rayon et force configurables. Appliquer un champ circulaire tangentiel sur les cellules voisines.

Algorithme

Interpolation Bilinéaire

Échantillonnage aux 4 coins de la cellule avec pondération (1-tx)(1-ty), évitant les artefacts de marche entre cellules.

Algorithme

Diffusion Thermique

Transfert de chaleur par convolution 3×3, avec zones thermiques saisonnières et modificateurs dynamiques (sorts, heaters).

Des courants marins invisibles 🌊

L'eau n'est pas immobile ! Des courants poussent les poissons dans différentes directions, comme des tapis roulants invisibles. La température varie aussi : les zones chaudes attirent certaines espèces, les zones froides en repoussent d'autres.

⚙️ 3.5 Les 8 Étapes de la Simulation3.5 Pipeline de Simulation en 8 Phases

⚙️ 8 étapes, 60 fois par seconde, sans accroc

Le moteur recalcule tout 60 fois par seconde. Chaque calcul suit 8 étapes dans un ordre précis — comme une chaîne d'assemblage automobile où chaque poste fait une tâche spécifique.

L'ordre est critique : les prédateurs bougent avant le flocking pour que les proies réagissent au danger actuel. Chaque phase est un module JS isolé (~200 à 1000 lignes), testable et profilable indépendamment. Le pipeline complet tourne en moins de 8ms à 10K boids.

🧪 Le pipeline en pratique

Le pipeline est séquentiel par nécessité : la phase de flocking a besoin des résultats de la grille spatiale. On ne peut pas les paralléliser. En revanche, les calculs internes à chaque phase sont parallélisables (un jour avec SharedArrayBuffer + Workers).

Le pattern "pipeline de stages" est identique à ce qu'on trouve dans les systèmes ECS (Entity Component System) des moteurs de jeu modernes. Même Unity est passé à ce modèle avec DOTS. C'est prouvé en production.

Phase 1 — Timers & Cooldowns

Décrément des timers de reproduction, sorts, états. Scheduling d'événements.

Phase 2 — Objets Dynamiques

Update nourriture, pièges, filets, vortex, obstacles. Spawning/despawning.

Phase 3 — Environnement

Flow field, température, cycle jour/nuit, saisons. Shockwaves, tsunamis.

Phase 4 — Auto-Balance Écosystémique

Ratio prédateur/proie → ajustement dynamique spawn, énergie, reproduction.

Phase 5 — Biologie & Grid Rebuild

Aging, énergie, satiété, mort naturelle, gestation. Rebuild SpatialGrid CSR.

Phase 6 — Prédateurs

IA de chasse, territoralité, frenzy mode. Met à jour AVANT flocking.

Phase 7 — Flocking (cœur)

Cluster → Individual → Deferred → PostPhysics. 4 sous-modules, ~70K LOC total.

Phase 8 — Post-Processing

Stats, nettoyage, reproduction spawn, événements, spatial grid rebuild food.

Répartition du Temps CPU par Phase (100K boids, perfLevel 1)
P7 Flocking
~9ms (55%)
P5 Biology
~3ms (18%)
P6 Predators
~1.6ms (10%)
P3 Environment
~1.3ms (8%)
P2 Objects
~0.8ms (5%)
P8 PostProcess
~0.6ms (4%)

Ce qu'il faut retenir

14 milliards de comparaisons réduites à quelques milliers grâce au spatial hashing. 15 forces combinées pour un comportement réaliste. Un pipeline en 8 phases qui organise tout comme une chaîne de production industrielle — le tout en moins de 8ms.

Une chaîne de montage en 8 étapes ⚙️

À chaque image affichée, le moteur exécute 8 étapes dans l'ordre : trouver les voisins → calculer les forces → bouger les poissons → gérer l'écosystème → dessiner. Comme dans une usine, chaque poste fait son travail et passe le relais au suivant.

8phases séquentielles
< 16mspar image
60images/seconde
Étude Technique N°4
🧭
Le GPS invisible de l'océan

Les poissons savent nager en groupe. Mais comment les guider vers la nourriture, les éloigner des dangers, et créer des courants marins — sans scanner chaque entité ? Le champ de potentiel est une carte invisible qui orchestre tout cela en O(1).

Champs de Potentiel & Navigation Intelligente

Comment des milliers de poissons évitent-ils les requins et trouvent-ils la nourriture sans se téléphoner ? 📱 Grâce à une carte invisible qui leur dit "ici c'est dangereux" et "là-bas il y a à manger" !

Champ de potentiel inspiré de la robotique (Khatib 1986). La grille 64×36 = compromis précision/performance. Le blend progressif flocking→guidage est la clé de la scalabilité. 8 couches de signaux empilables indépendantes.

64×36
Grille de Potentiel
2304 cellules de navigation
InstantanéO(1)
Gradient Lookup
Navigation sans scan de voisins
15
Forces de Flocking
Reynolds + 12 extensions
3 Tiers
LOD Navigation
Individual → Cluster → Field

🧲 4.1 Architecture du Champ de Potentiel

⚙️ Un GPS qui coûte presque rien en calcul

Le problème classique : si chaque poisson devait scanner l'océan pour trouver la nourriture et repérer les prédateurs, le coût serait astronomique à 100K entités.

La solution : une carte invisible de 64×36 cases. Les sources de danger (requins, obstacles) et d'attraction (nourriture, refuges) "émettent" un signal qui se diffuse dans les cases voisines. Chaque poisson lit simplement la pente locale de cette carte pour savoir où aller.

📊 Résultat business : navigation de 100K entités avec un coût quasi-nul. C'est le même principe que les CDN (Content Delivery Network) — au lieu de demander au serveur central, on lit la copie la plus proche.

🧪 Pourquoi la grille 64×36

La résolution de la grille (64×36) est un compromis. Plus fin = plus précis mais plus lent. Plus grossier = plus rapide mais les poissons "sentent" les cases. À 64×36, chaque case fait ~30px — assez fin pour un guidage naturel, assez gros pour une mise à jour rapide.

C'est le même dilemme que la résolution de texture en jeu vidéo : 4K est beau mais coûteux. Ici, "basse résolution" suffit car les poissons lissent le résultat par leur mouvement continu.

┌───────────────────────────────────────────────────────────────┐ │ POTENTIAL FIELD LIFECYCLE (per frame) │ ├───────────────────────────────────────────────────────────────┤ │ 1. CLEAR (sparse) — reset dirty cells only │ │ 2. SCATTER — sources → attract/repulse += w/dist² │ │ 3. DIFFUSE (1/4f) — box blur 3×3 × decay factor │ │ 4. READ (per boid) — gradient = ∇attract − ∇repulse, O(1) │ └───────────────────────────────────────────────────────────────┘
Source Type Poids Rayon Analogie
🍖 Nourriture Attraction 1.0 3 cells ⟹ Produit populaire sur un marketplace
🏠 Refuge Attraction 2.0 5 cells ⟹ Zone de sécurité réseau
🦈 Prédateur Répulsion 3.0 4 cells ⟹ Menace de sécurité détectée
💥 Shockwave Répulsion 5.0 6 cells ⟹ Alert critique système
🪨 Obstacle Répulsion 1 cell ⟹ Hard limit / firewall
🌡️ Zone chaude Attraction sélective 0.8 4 cells ⟹ Feature flag par segment

Une grille magique sous la mer ✨

Imaginez une grille invisible de 64×36 cases posée au fond de l'océan. Chaque case contient un chiffre : positif = "viens ici", négatif = "fuis". Les poissons "lisent" leur case et nagent en conséquence. Simple mais très efficace !

2 304 cases pour guider 100 000 poissons

La grille ne fait que 64 × 36 = 2 304 cases. Chaque poisson regarde juste 1 case. Pas besoin de calculer les distances avec tous les autres !

🐠 4.2 Interaction Flocking × Potentiel par Tier

⚙️ L'astuce qui économise 80% du calcul

Les 100K poissons ne font pas tous le même travail. Le moteur les classe en 3 niveaux :

🔴 Proches (20%) — Calcul complet : 15 forces de navigation individuelle

🟡 Moyens (30%) — Calcul simplifié : moyennes par cluster + carte

🟢 Lointains (50%) — Carte uniquement : un seul lookup O(1)

En entreprise, c'est exactement le principe du tiered caching : données chaudes en RAM, tièdes en SSD, froides en archive. Le résultat perçu est identique, le coût est divisé par 5.

🧪 Le blend en pratique

Le blend tier0/tier1/tier2 (15% / 50% / 90% champ de potentiel) est tuné à l'œil. Les valeurs théoriques "optimales" n'existent pas — ça dépend du comportement visuel recherché. J'ai itéré pendant des jours pour trouver le bon feeling.

Le pattern de blend linéaire fonctionne bien ici. Dans d'autres projets, j'ai dû utiliser des courbes ease-in/ease-out pour que la transition soit invisible. L'important : que le joueur ne remarque JAMAIS le changement de LOD.

Répartition des Sources de Force par Tier LOD
Tier 0 — Reynolds
85% — Forces individuelles
Tier 0 — Potentiel
15% — Correction
Tier 1 — Reynolds
50% — Cluster
Tier 1 — Potentiel
50% — Navigation macro
Tier 2 — Reynolds
10%
Tier 2 — Potentiel
90% — Guidage total
👀

Poissons proches

Suivent les 3 règles naturelles (séparation, alignement, cohésion) — le plus réaliste

🗺️

Poissons lointains

Suivent la carte invisible — moins cher à calculer, tout aussi convaincant

💼 Le secret de l'extensibilité

C'est cette transition intelligente qui permet de passer de 1 000 à 100 000 poissons sans changer l'expérience utilisateur. Les poissons proches ont le comportement réaliste, les lointains sont plus simples. L'utilisateur ne voit aucune différence.

🌊 4.3 Propagation des Signaux

⚙️ Propagation intelligente à coût minimal

Le signal de danger d'un requin ne se propage pas instantanément — il se diffuse progressivement dans les cases voisines, comme un message qui se transmet de bouche à oreille.

Pour économiser du calcul, cette diffusion ne se fait que 1 frame sur 4. Résultat : un signal qui atteint les poissons à 5+ cases de distance, avec un coût de seulement ~0.2ms. Les poissons réagissent de manière coordonnée, créant des mouvements de foule spectaculaires.

🧪 Vitesse de propagation

Sans throttling, le blur converge en ~20 itérations (la diagonale de la grille). Avec un blur toutes les 3 frames, ça prend ~60 frames (~1 seconde). C'est impceptible pour le joueur car les sources sont déjà perceptibles localement.

Si les sources apparaissent trop lentement, augmenter le rayon initial d'influence (scatter radius) est plus efficace que d'augmenter la fréquence du blur. C'est un truc qu'on apprend en optimisant des simulations de particules.

Sans Diffusion
Local
Seuls les poissons adjacents
au requin réagissent
Avec Diffusion
Global
Onde de fuite coordonnée
sur 5+ cellules de rayon

Comme une goutte d'encre dans l'eau 🎨

Quand on place de la nourriture, le signal ne se propage pas instantanément. Il se diffuse lentement autour de sa source, comme une goutte d'encre dans un verre d'eau. Plus on est loin, plus le signal est faible.

⚖️ 4.4 Les 15 Forces de Navigation

⚙️ 15 règles simples = comportement complexe

Chaque poisson combine 15 "forces" qui le poussent dans différentes directions. Individuellement, ces forces sont triviales ("s'éloigner du danger", "suivre le groupe"). Mais combinées sur 100 000 poissons, elles produisent des comportements de groupe spectaculaires — formation en V, éclatement face au prédateur, migration saisonnière.

C'est le même phénomène qu'on observe dans la vraie nature, et c'est aussi le principe des systèmes multi-agents utilisés en logistique et en IA.

🧪 L'architecture globale du champ

Le champ de potentiel avec 8 couches (nourriture, danger, refuge, courant, température, profondeur, banc allié, zone de reproduction) est inspiré des architectures robotiques (Khatib 1986). En robotique, c'est le standard pour la navigation autonome.

L'avantage clé : chaque couche est indépendante. On peut ajouter une 9ème couche (ex: zones de peur) sans modifier le reste du code. C'est de la vraie extensibilité architecturale.

# Force Source Comportement Émergent
1 Séparation Voisins Anti-collision, espacement naturel
2 Alignement Voisins/Cluster Bancs synchronisés
3 Cohésion Voisins/Cluster Regroupement en bancs
4 Fuite prédateur SpatialGrid Dispersal, évasion
5 Recherche food SpatialGrid Convergence vers nourriture
6 Draft (aspiration) Voisins V-formation, file indienne
7 Courant marin FlowField Dérive passive naturelle
8 Obstacle avoid SpatialGrid Contournement fluide
9 Edge repulsion World bounds Confinement au monde
10-11 Potentiel ± PotentialField Navigation macro
12 Température FlowField Migration thermique
13 Mate attraction Flags Rapprochement reproduction
14 Vortex Effects Aspiration tourbillon
15 Formation Race config Patterns spécifiques espèce
💪

15 envies en même temps !

Chaque poisson ressent 15 forces simultanées : rester avec le groupe, fuir le prédateur, chercher la nourriture, suivre le courant… Le moteur fait un "vote" entre toutes ces envies pour décider où nager. La fuite est toujours prioritaire ! 🏃‍♂️

Ce qu'il faut retenir

Une carte invisible de 2304 cases guide 100 000 poissons en O(1) par entité. 15 forces de navigation se combinent pour produire des comportements collectifs spectaculaires — formation, dispersion, migration — sans aucun "chef d'orchestre". Le coût total : moins de 1ms pour l'ensemble du système.

Étude Technique N°6
🌿
L'écosystème : de la physique à la vie

Les poissons sont visibles, intelligents, et guidés. Mais un aquarium sans écosystème, c'est juste des points qui bougent. Cette étude ajoute la couche biologique : énergie, faim, reproduction, prédation. Le résultat : un monde vivant qui s'auto-régule.

Simulation d'Écosystème Vivant : Physique, Comportement & Équilibre Dynamique

C'est comme un vrai aquarium vivant ! 🐠 Les poissons naissent, mangent pour avoir de l'énergie, grandissent, font des bébés... et finissent par mourir. Les requins chassent les petits poissons, qui fuient en banc. Et le plus fou : personne ne contrôle rien — l'équilibre se crée tout seul, comme dans la nature ! 🌊

Oscillations de population conformes au modèle Lotka-Volterra. Système d'énergie avec boucles de rétroaction non-linéaires. IA prédateur par FSM à 4 états. Stabilité du point fixe vérifiée empiriquement.

100%
Émergent
Aucune règle globale
4 États
IA Prédateur
Patrol → Chase → Frenzy → Rest
Cycle de Vie
Naissance → Croissance → Mort
Auto-réguléLotka-Volterra
Dynamique
Équilibre prédateur-proie

🌊 6.1 Architecture de l'Écosystème

⚙️ Un écosystème qui se gère tout seul

8 types d'entités (nourriture, prédateurs, refuges, obstacles, vortex, zones de peur...) et 30+ types d'événements créent un écosystème 100% émergent. Le moteur auto-régule les populations : si les prédateurs se multiplient trop, les proies diminuent → les prédateurs meurent de faim → les proies reviennent. C'est le cycle de Lotka-Volterra en action.

🧪 Lotka-Volterra au quotidien

Le modèle prédateur-proie de Lotka-Volterra (1925) prédit des oscillations cycliques. On les observe en temps réel dans la simulation : les proies montent → les prédateurs se multiplient → les proies chutent → les prédateurs meurent de faim → les proies reviennent.

En prod, le risque c'est l'extinction. Si les oscillations sont trop amples, une espèce peut tomber à 0 et ne jamais revenir. Le système ajoute un "filet de sécurité" : si une espèce tombe sous 50 individus, le taux de reproduction est boosté. C'est un pattern de circuit-breaker bio.

┌───────────────────────────────────────────────────────────────┐ │ ECOSYSTEM SUBSYSTEM │ ├───────────────────────────────────────────────────────────────┤ │ │ │ ENTITÉS DYNAMIQUES ÉVÉNEMENTS GLOBAUX │ │ ├─ 🍖 Food (6 types) ├─ 🌊 Tsunami │ │ ├─ 🪤 Traps ├─ 🦈 Predator Surge │ │ ├─ 🕸️ Nets ├─ 🍖 Abundance Event │ │ ├─ 🌀 Vortex ├─ 🌡️ Temperature Shift │ │ ├─ 🪨 Obstacles ├─ 🧬 Genetic Drift │ │ ├─ 🏠 Refuges ├─ 💥 Shockwave │ │ ├─ 🌿 Seaweed (végétation) ├─ 🌙 Marée Noire │ │ └─ 💀 Fear zones └─ ⚡ Lightning Storm │ │ │ │ AUTO-BALANCE ENGINE │ │ ├─ Ratio prédateur/proie → spawn/despawn dynamique │ │ ├─ Population caps par espèce (maxSchoolSize) │ │ ├─ Énergie globale → modulation reproduction │ │ └─ Extinction detection → slot recycling (MutationManager) │ │ │ └───────────────────────────────────────────────────────────────┘
Système Fichier Source LOC Rôle
Ecosystem ecosystem.js 324 Spawning/despawning d'objets, événements, fear zones
Phase Biology phase-biology.js ~450 Aging, énergie, satiété, mort naturelle, gestation
Phase Balance phase-balance.js ~280 Auto-équilibrage populations, ratios proie/prédateur
Phase Predators phase-predators.js ~600 IA de chasse, frenzy mode, territorialité
Flow Field flow-field.js ~400 Courants marins, température, diffusion
Mutation Manager mutation-manager.js 811 Spéciation, crossover génomique, naming, slot recycling

Un vrai écosystème vivant 🌊

Ce n'est pas juste des poissons qui nagent ! C'est un écosystème complet : la nourriture pousse, les poissons mangent et grandissent, ils se reproduisent quand ils ont assez d'énergie, les prédateurs chassent… Et personne ne contrôle rien — tout émerge naturellement des règles !

6.2 Modèle Énergétique & Cycle de Vie

⚙️ Deux jauges de vie pour des comportements réalistes

Chaque poisson possède deux jauges : l'énergie (vivre) et la satiété (se reproduire). Les deux déclinent à chaque instant — nager coûte de l'énergie. Se nourrir les recharge. Quand la satiété dépasse un seuil, le poisson peut se reproduire. Quand l'énergie tombe à zéro, il meurt.

Ce système simple produit des dynamiques écologiques émergentes : en abondance, la population explose ; en pénurie, la mortalité régule naturellement. Aucune règle globale ne pilote l'équilibre — il émerge des interactions individuelles.

🧪 Le système d'énergie vu par un senior

L'énergie est la monnaie de l'écosystème. Chaque action a un coût énergétique calibré par itérations successives. Le ratio manger/dépenser détermine la population d'équilibre. Trop généreux → explosion démographique. Trop strict → extinction.

Le tuning est un art : j'ai passé des dizaines d'heures à ajuster les constantes pour obtenir des oscillations stables mais pas monotones. La clé c'est la reproduction conditionnelle (énergie > 80) qui crée un seuil non-linéaire dans la dynamique.

energy(t+1) = energy(t) − drain × speed² × dt + food_eaten × nutrition
satiety(t+1) = satiety(t) − satietyDrain × dt + food_eaten × satNutrition
Si energy ≤ 0 → mort par famine | Si satiety ≥ threshold → reproduction possible
Équation 12 — Modèle Énergétique Dual (Survie vs Reproduction)
Comportement

Arbitrage Faim vs Sécurité

Un poisson affamé (energy < 30%) ignore les prédateurs proches pour chercher de la nourriture. Ce n'est pas un bug, c'est biologiquement réaliste : la survie immédiate prime sur l'évitement du danger quand la mort par famine est imminente.

Comportement

Reproduction Conditionnelle

La reproduction nécessite : (1) satiety > 80%, (2) cooldown expiré, (3) mate de même espèce à portée, (4) population < maxSchoolSize. Chaque condition est évaluée indépendamment pour créer des patterns spatio-temporels naturels.

Performance

Drain Paramétrique

Chaque espèce a ses propres energyDrainMult et satietyDrainMult. Résultat : certaines espèces sont des « sprinters » qui consomment vite mais chassent efficacement, d'autres sont des « endurantes » qui survivent longtemps avec peu.

Budget énergétique typique d'un poisson (sur 5 minutes de simulation)
Nage (base)
35% — drain passif proportionnel à la vitesse
Fuite prédateur
25% — boost de vitesse ×2 = drain ×4
Recherche food
15% — accélération vers nourriture
Reproduction
15% — coût fixe par bébé produit
Idle / warming
10% — température non-optimale
🔋

Chaque poisson a sa barre d'énergie

Manger = +25 énergie. Nager = -0.05/seconde. Fuir un prédateur = coûte encore plus. Se reproduire = -30 énergie. Quand l'énergie tombe à zéro... le poisson meurt. 😢

💼 Régulation naturelle

Ce système d'énergie crée une régulation automatique de la population. Trop de poissons → pas assez de nourriture → moins d'énergie → moins de reproduction → la population diminue. C'est le même principe que les marchés financiers (offre/demande).

🦈 6.3 Intelligence Artificielle des Prédateurs

⚙️ Des prédateurs intelligents qui s'adaptent

Les prédateurs ont une IA à 3 états : patrouille (errance), chasse (poursuite ciblée), et frénésie (attaque massive quand très affamé). En chasse, le prédateur émet une onde de choc dans le champ de potentiel, faisant fuir les proies — créant les mouvements de panique spectaculaires des vrais bancs.

Le passage entre états dépend de la faim et de la distance aux proies. Ce simple automate à 3 états suffit à produire des dynamiques prédateur-proie réalistes et spectaculaires.

🧪 L'IA prédateur en pratique

Les machines à états (FSM) sont le B.A.-BA de l'IA de jeu vidéo — utilisées depuis Pac-Man (1980). Le danger : l'explosion combinatoire. Avec 4 états, c'est gérable. Avec 10 états et des transitions conditionnelles, ça devient un cauchemar à debugger.

Ici, 4 états suffisent car le comportement émerge de l'interaction entre prédateurs et proies, pas de la complexité individuelle. C'est le principe fondamental de l'IA émergente : des règles simples → des comportements complexes.

🔵 État: Patrouille

Le prédateur dérive dans son territoire (rayon configurable). Il scanne les proies dans un rayon de détection large. Si une proie est détectée → transition vers Chasse.

// Patrouille: waypoint + scan
if (distToWaypoint < 30) {
    waypoint = randomInTerritory();
}
target = nearestPrey(scanRadius);

🔴 État: Chasse

Poursuite directe de la cible avec accélération boost ×2.5. Si la proie s'échappe (distance > loseRadius) → retour Patrouille. Si contact → kill + énergie + potentiel transition Frenzy.

// Chasse: pursuit + intercept
accel = dir(target) * boostForce;
if (dist < killRange) {
    kill(target);
    killStreak++;
}

💡 Frenzy Mode (mode frénésie)

Après 3+ kills en séquence rapide, le prédateur entre en Frenzy : vitesse ×3, rayon de détection ×2, durée 5 secondes simulées. Les proies à proximité émettent des shockwaves de peur qui se propagent via le champ de potentiel. Résultat visuel : explosion dispersive du banc.

Les prédateurs ont un cerveau ! 🧠

Chaque prédateur a 4 comportements :
🚶 Patrouille — il nage au hasard en cherchant des proies
🏃 Poursuite — il a repéré une proie et fonce dessus
😤 Frénésie — il est très proche et accélère !
😴 Repos — il digère après avoir mangé

🎮 6.4 Démo Interactive : Mini-Écosystème

⚙️ Cycle prédateur-proie en temps réel

Écosystème simplifié : proies (bleu) fuient les prédateurs (rouge), mangent la nourriture (vert), se reproduisent avec assez d'énergie. L'équilibre émerge naturellement. Observez le graphe de population en bas du canvas.

🧪 La simulation comme preuve

La simulation confirme le modèle théorique : les oscillations de population suivent les cycles prédateur-proie. Le bruit stochastique (positions aléatoires, rencontres probabilistes) empêche les cycles parfaitement réguliers — exactement comme dans la nature.

L'intérêt n'est pas la précision mathématique mais la vraisemblance visuelle — le joueur doit "sentir" que l'écosystème est vivant et auto-régulé.

🌊 Mini-Écosystème Interactif

🎯 Le but : Observe le cycle proie/prédateur en temps réel. Clique pour lâcher de la nourriture (🍖), clic droit pour ajouter un requin (🦈). Regarde le graphe de population !
2 000
5
15/s
30%
Proies: 0
Prédateurs: 0
Nourriture: 0
Naissances: 0
Morts: 0

Ce qu'il faut retenir

Un écosystème complet émerge de quelques règles simples : énergie, faim, reproduction, prédation. Aucune règle globale ne contrôle les populations — l'équilibre naît des interactions individuelles. C'est de l'IA émergente appliquée, pas de la programmation classique.

Étude Technique N°7
🧬
L'évolution : chaque poisson est unique

L'écosystème vit, se nourrit et se reproduit. Mais chaque poisson se ressemble. Cette étude ajoute l'ADN numérique : 28 gènes qui contrôlent taille, couleur, vitesse, comportement. Les espèces naissent, évoluent, mutent et s'éteignent — comme dans la vraie nature.

Génétique Paramétrique, Spéciation & Arbre Phylogénétique

Chaque poisson est unique ! Son apparence — forme, couleur, motifs — est déterminée par un ADN numérique de 28 gènes. Les bébés héritent des traits de leurs parents avec de petites mutations, créant de nouvelles espèces au fil du temps.

28 paramètres par espèce, mutations directionnelles non-convergentes, seuil de similarité à 85%. La rareté est calibrée comme dans les jeux de collection (50% Commun → 2% Légendaire). Le drift empêche la convergence vers la moyenne — clé de la diversité.

28
Gènes par Espèce
Morphologie, couleur, comportement
5 Tiers
Système de Rareté
Commun → Légendaire
Espèces Possibles
Mutations + Crossing génétique
Live
Arbre Phylogénétique
Spéciation en temps réel

🧬 7.1 Structure du Génome : 28 Gènes Continus

⚙️ Un ADN numérique à 28 gènes

Chaque espèce est définie par un génome de 28 gènes (valeurs entre 0 et 1) encodant la forme du corps, les nageoires, les couleurs, les motifs, la vitesse et l'agressivité. Le shader lit directement ces valeurs pour générer la morphologie unique de chaque poisson.

Les 28 gènes forment un espace à 28 dimensions où chaque point = une espèce possible. Deux espèces proches dans cet espace se ressemblent ; deux espèces éloignées sont radicalement différentes.

🧪 28 gènes : pourquoi ce nombre

28 gènes = un compromis entre diversité (plus de gènes = plus de combinaisons) et contrôlabilité (moins de gènes = plus facile à tuner). Avec 28 valeurs continues [0,1], l'espace des possibles est astronomique — assez pour que chaque espèce soit unique.

En pratique, seuls ~8 gènes ont un impact visuel fort (couleurs, taille, forme de queue). Les 20 autres contrôlent des stats invisibles (vitesse, endurance, agilité). C'est intentionnel : ça crée de la diversité fonctionnelle même entre des espèces qui se ressemblent.

Index Gène Plage Effet Visuel
0 bodyElongation [0, 1] Ratio longueur/hauteur du corps
1 bodyWidth [0, 1] Épaisseur latérale
2 headPointiness [0, 1] Profil frontal (arrondi → pointu)
3 jawProminence [0, 1] Taille de la mâchoire
4 dorsalHeight [0, 1] Hauteur nageoire dorsale
5 dorsalLength [0, 1] Longueur nageoire dorsale
6 tailForkDepth [0, 1] Profondeur fourche caudale
7 tailSpan [0, 1] Envergure de la queue
8-14 Appendices [0, 1] finRayLength, pectoralSize, analFinSize, ventral...
15-22 Extras [0, 1] tentacleCount, shellCoverage, clawSize, antennae...
23-27 Patterns [0, 1] patternType, patternScale, patternContrast, accentHue, luminance

🧬 Variation combinatoire

Avec 28 gènes continus, l'espace génétique est de dimension 28 à valeurs continues. L'espace des phénotypes visuellement distincts est pratiquement infini. Après 1 heure de simulation, le joueur observe typiquement 50-100 espèces uniques.

L'ADN, c'est 28 curseurs 🎛️

Imagine 28 curseurs qu'on peut déplacer de 0 à 100%. Chaque curseur contrôle un aspect du poisson : taille, couleur, vitesse, forme de la queue… Comme les combinaisons sont infinies, chaque espèce est unique !

🌈

Des milliards de combinaisons

Avec 28 paramètres continus, le nombre de combinaisons possibles dépasse 10 milliards de milliards. Chaque espèce qui naît est vraiment unique dans l'histoire de la simulation !

🔀 7.2 Crossover, Mutation & Drift Directionnel

⚙️ Comment les espèces évoluent

À chaque reproduction, les gènes de l'enfant sont un mélange des parents, avec une mutation aléatoire. Mais POISSON utilise un drift directionnel : la mutation pousse dans une direction cohérente plutôt que de disperser au hasard.

Au fil des générations, les descendants dérivent dans l'espace génétique, créant de nouvelles variétés. Sans ce mécanisme, les mutations aléatoires convergeraient vers une moyenne identique. Le drift garantit que la diversité augmente — exactement comme dans la nature.

🧪 Le drift : l'ingrédient secret

Sans drift directionnel, les mutations aléatoires convergent vers la moyenne (loi des grands nombres). Toutes les espèces finissent par se ressembler. Le drift résout ça : chaque mutation a 70% de chance d'amplifier la divergence au lieu de la réduire.

C'est inspiré de la sélection naturelle réelle : dans la nature, les mutations vers des niches écologiques nouvelles sont favorisées. Le paramètre 70/30 est tuné pour maximiser la diversité visuelle tout en gardant des espèces "crédibles".

// mutation-manager.js — mutateValue() réel
// Drift directionnel : 70% pousse LOIN de la moyenne, 30% aléatoire
function mutateValue(a, b, deviation) {
    const blend = 0.2 + fastRandom() * 0.6;   // Ratio parent (0.2–0.8)
    const base = a * blend + b * (1 - blend);  // Pas centré sur 0.5 !
    const avg = (a + b) * 0.5;
    // 70% : amplifie la direction de divergence
    const drift = fastRandom() < 0.7
        ? (base > avg ? 1 : -1)     // Pousse plus loin du centre
        : (fastRandom() * 2 - 1);   // 30% : bruit pur
    const delta = base * deviation * Math.abs(fastRandom() * 2 - 1) * drift;
    return Math.max(0.01, base + delta);
}

// crossoverGenome() — Uniform crossover avec mutation morphologique
function crossoverGenome(genomeA, genomeB, mutRate, mutDev, driftRate) {
    const child = new Float32Array(GENE_COUNT);
    for (let i = 0; i < GENE_COUNT; i++) {
        // 50/50 parent selection
        child[i] = fastRandom() < 0.5 ? genomeA[i] : genomeB[i];
        // Mutation chance per gene
        if (fastRandom() < mutRate) {
            child[i] += (fastRandom() * 2 - 1) * mutDev;
        }
        // Drift: push towards extreme values over generations
        if (fastRandom() < driftRate) {
            child[i] += child[i] > 0.5 ? 0.05 : -0.05;
        }
        child[i] = Math.max(0, Math.min(1, child[i]));
    }
    return child;
}
Gchild[i] = select(GA[i], GB[i]) + N(0, σ) × drift_direction
drift = sgn(Gchild[i] − avg(GA[i], GB[i])) avec prob 0.70
Divergence cumulée après N générations ≈ O(√N × σ)
Équation 13 — Drift Directionnel et Divergence Cumulative

Bébé poisson = papa + maman + surprise 🎲

Quand deux poissons se reproduisent, chaque gène du bébé est pris au hasard chez le père ou la mère. Puis une petite mutation aléatoire peut modifier certains gènes. C'est comme ça que naissent de nouvelles espèces au fil des générations !

💼 Algorithmes génétiques appliqués

Ce système de crossover + mutation est exactement celui utilisé en intelligence artificielle pour optimiser des solutions. Les algorithmes génétiques résolvent des problèmes de logistique, de design et de machine learning dans l'industrie.

💎 7.3 Système de Rareté & Taxonomie

⚙️ Des espèces rares comme dans un collectible game

Quand deux espèces de tiers différents se croisent, leur descendant hérite d'un niveau de rareté — Commun, Rare, Épique ou Légendaire. Plus les parents sont génétiquement éloignés, plus la rareté est élevée.

Un système anti-exploitation empêche de « farmer » les raretés : si les parents sont trop similaires, le croisement ne produit que du commun. Le résultat : des événements de spéciation rares et valorisés, qui émergent naturellement de l'écosystème.

🧪 Le game design de la rareté

Le système de rareté est inspiré des jeux de collection (Pokémon, gacha games). La rareté doit être corrélée à l'unicité réelle — sinon le joueur sent que c'est artificiel. C'est pourquoi le calcul combine divergence génétique + stats extrêmes.

Distribution cible : ~50% Commun, ~25% Peu Commun, ~15% Rare, ~8% Épique, ~2% Légendaire. Ces ratios sont standard en game design et créent le bon feeling de "collectionnabilité".

Rareté Condition Bonus Stats Couleur Traits Spéciaux Possibles
⬜ Commun Même espèce ou tier adjacent ×1.0 #b0bec5
🟢 Peu commun Cross-tier ≥ 1 ×1.35 #66bb6a Résistance+
🔵 Rare Cross-tier ≥ 2 ou tierSum ≥ 5 ×1.70 #42a5f5 Résistance+, Fertile
🟣 Épique Cross-tier ≥ 3 ou tierSum ≥ 5 ×2.05 #ab47bc + Véloce, Titanesque
🟠 Légendaire Cross-tier ≥ 3 + tierDiff ≥ 3 ×2.40 #ffa726 + Omnivore, Prédateur Alpha

💡 Système de Nommage : 10 000+ Noms Uniques

Le MutationManager génère des noms taxonomiques en combinant :
8 familles de préfixes (Classical, Power, Greek, Oceanic, Mutation, Environ, Mythic, Trait) × 10 préfixes chacune = 80 préfixes
20 suffixes latins (-us, -is, -ax, -oid, -ella, -opsis...)
30 combinaisons curées uniques (Baleine+Sardine = "Leviathan", Requin+Poulpe = "Krakenodon")
Cross-espèce : portmanteau avec point de coupe variable (35-65% de chaque nom parent).
Formaté en chiffres romains pour les homonymes (Neosardine II, III...).

Comme dans un jeu de collection ! ⭐

Chaque espèce reçoit un niveau de rareté :
Commun (~50%) — les espèces classiques
🟢 Peu Commun (~25%) — légèrement différentes
🔵 Rare (~15%) — des caractéristiques uniques
🟣 Épique (~8%) — vraiment spéciales
🟡 Légendaire (~2%) — extrêmement uniques !

🎮 7.4 Démo Interactive : Labo d'Évolution

⚙️ Observez l'évolution en action

Cette démo simule le crossover génétique. Chaque point = une espèce. Les couleurs et positions reflètent la divergence génétique au fil des générations. Ajustez le taux de mutation et le drift pour voir comment la diversité évolue.

🧪 Mesurer la diversité

La divergence est mesurée comme la distance maximale entre deux espèces dans l'espace des gènes. Si toutes les espèces se ressemblent, la divergence est faible → le drift est trop bas. Si les espèces sont toutes extrêmes, le drift est trop haut.

La valeur idéale de divergence est autour de 0.3-0.5. En dessous, on augmente le drift automatiquement. Au-dessus, on le réduit. C'est un auto-tuning qui maintient la diversité à un niveau visuellement intéressant.

🧬 Labo de Génétique

🎯 Le but : Clique "Lancer l'évolution" pour voir comment les espèces divergent à chaque génération. Clique sur le canvas pour injecter une nouvelle espèce. Ajuste la mutation et le drift pour voir leur impact sur la diversité !
30%
15%
5%
10
Espèces créées: 0
Génération: 0
Divergence max: 0
🔬

L'évolution en accéléré

En quelques secondes, vous pouvez observer des dizaines de générations d'évolution. Les points colorés représentent les espèces, les anneaux les générations. Plus un point est loin du centre, plus l'espèce est récente.

♻️ 7.5 Détection de Similarité & Recyclage d'Espèces

⚙️ Anti-doublon intelligent

Avant de créer une nouvelle espèce, le système compare le candidat avec toutes les espèces existantes sur 21 critères pondérés. Si la similarité dépasse 85%, le bébé est absorbé dans l'espèce la plus proche. Résultat : chaque espèce reste visuellement et statistiquement distincte.

🧪 Le seuil de 85% en pratique

Le seuil de 85% a été trouvé empiriquement. À 80%, on crée trop d'espèces quasi-identiques (pollution visuelle). À 90%, les mutations mineures créent de nouvelles espèces trop facilement → on atteint le cap de 64 espèces trop vite.

85% est le sweet spot. Le poids ×3 sur le genome (vs ×1 pour les stats numériques) assure que deux espèces avec les mêmes stats mais des couleurs différentes ne sont pas fusionnées. La couleur est le premier critère visuel du joueur.

// _computeSimilarity() — Extrait réel de mutation-manager.js
// 21 champs numériques pondérés + defense + diet + genome + food prefs
_computeSimilarity(candidate, existing) {
    const numericFields = [
        { key: 'speedMult', weight: 2.0 },
        { key: 'forceMult', weight: 1.5 },
        { key: 'sizeMult', weight: 1.5 },
        { key: 'energyEfficiency', weight: 1.0 },
        { key: 'cohesionMult', weight: 1.0 },
        { key: 'separationMult', weight: 1.0 },
        { key: 'tempPref', weight: 1.5 },
        { key: 'fovMult', weight: 0.5 },
        // ... + 13 autres champs
    ];
    // Normalized diff per field × weight → weighted average
    for (const { key, weight } of numericFields) {
        const diff = Math.abs(a - b) / Math.max(|a|, |b|, 0.001);
        totalMatch += weight * Math.max(0, 1 - diff);
    }
    // + Genome similarity (poids 3.0) + Diet match (poids 2.0)
    // + Defense match (poids 2.0) + Food prefs (poids 1.5)
    return totalMatch / totalWeight;  // → [0, 1]
}
Perf

Slot Recycling

Quand RACES.length >= maxSpecies, les espèces éteintes les plus anciennes sont recyclées. Leur slot dans le tableau RACES[] est réécrit avec la nouvelle espèce. La matrice d'affinité RACE_AFFINITY[N×N] est mise à jour in-place. Résultat : allocation zéro.

Algorithme

Extinction Detection

checkExtinctions(racePop) est appelé chaque frame dans phase-balance. Si racePop[r] === 0 pour une espèce mutante (index ≥ 16), elle entre dans extinctionOrder[]. Les espèces de base (0-15) ne sont jamais recyclées.

Algorithme

Affinity Matrix

Matrice N×N d'affinité inter-espèces. Un nouvel hybride hérite de la moyenne des affinités de ses parents : affinity[new][j] = (affinity[A][j] + affinity[B][j]) / 2. Utilisée pour le flocking inter-espèces.

Pas de doublons ! 🚫

Avant de créer une nouvelle espèce, le système vérifie qu'elle n'existe pas déjà. Si le bébé ressemble trop à une espèce existante (plus de 85% de similarité), il rejoint cette espèce au lieu d'en créer une nouvelle. Ça évite la pollution !

Ce qu'il faut retenir

28 gènes continus [0,1] encodent la morphologie complète d'un poisson. Le crossover et la mutation produisent une diversité infinie d'espèces visuellement distinctes. Le système de spéciation empêche la convergence génétique — chaque nouvelle espèce est réellement unique.