---
title: "Rainbow Journey — Lot 1 : Sous le capot du chemin chaud et de la robustesse"
subtitle: "Optimisations Three.js, coalescence React, persistance IndexedDB et accessibilite sans compromis sur la fluidite"
date: 2026-09-13
time: "20:14"
author: VFG / Rainbow Journey
publishedBy: idea
status: pub
tags: ["rainbowjourney", "performance", "three-js", "react", "indexeddb", "web-midi", "a11y", "devblog"]
---

![Rainbow Journey — Lot 1 : Sous le capot du chemin chaud et de la robustesse](../images/covers/2026-09-12-rainbow-journey-lot-1-optimisations-et-robustesse.png)


Apres avoir livre le premier jalon complet de Rainbow Journey (archive sous le tag `v0-astra-tango`), nous avons mene une seance d'audit approfondie sur le code reel. Lorsque l'on passe d'une demo statique a un jeu au clavier dense a 30 notes par seconde, les moindres inefficacites du chemin chaud deviennent des goulets d'etranglement majeurs.

Le **Lot 1** a ete concu pour repondre a un imperatif fondamental du cadrage : **garantir un traitement synchrone a latence minimale, un rendu 3D a 60 FPS sans hoquet garbage collector et une persistance robuste sans perte de donnees**.

Voici le recit technique et les arbitrages d'architecture de cette etape.

---

## 1. Le diagnostic du chemin chaud : pourquoi React et Three.js souffraient

Sous le jeu musical, un flux d'attaques et de relachements produit des dizaines d'evenements par seconde. En analysant le cycle de vie d'un message MIDI, trois problemes majeurs sont apparus :

1. **Re-rendus React massifs a chaque octet** : Chaque `note on` et `note off` appelait `notify()`, declenchant un cycle de reconciliation complet de `Journey` et des 88 touches de `Piano`. Dans chaque touche, `runtime.notes` etait appele 88 fois par rendu, regenerant des aplatissements de `Map` et des allocations d'objets inutiles.
2. **Pression GC dans la boucle Three.js (`World.tsx`)** : A chaque frame (60 fois par seconde), l'orientation des rubans clonait un `Vector3` par segment (`direction.clone().normalize()`), generant plus de 1 000 allocations de vecteurs par frame. De plus, `runtime.trails.filter().slice(-128)` allouait un tableau temporaire parmi 2 048 traces a chaque rafraichissement d'image.
3. **Persistance synchrone et journal sature** : Lors d'un enregistrement, chaque glissement de curseur dans l'editeur generait des centaines d'evenements `settings` contenant tout l'objet de configuration (schemas Zod, 32 regles, 28 effets). Toutes les 5 secondes, un `structuredClone` complet de la session etait valide et ecrit en ouvrant puis refermant une connexion IndexedDB a chaque appel.

---

## 2. Les solutions d'architecture implementees

Pour eliminer ces goulots sans casser la simplicite du code, nous avons decoupe le travail en paquets autonomes sans concurrence sur les fichiers.

### A. Rendu 3D sans allocation (`src/World.tsx`)
- **Vecteurs pre-alloues** : Utilisation d'un vecteur d'orientation statique `directionUnit` reinitialise via `.copy().normalize()`, supprimant totalement les instanciations de `Vector3` dans la boucle d'animation.
- **Parcours arriere borne** : Remplacement de l'allocation `.filter().slice(-128)` par une boucle arriere directe dans le tableau circulaire des traces, collectant jusqu'a 128 elements actifs dans une structure pre-allouee.
- **Cache d'uniformes et de terrain** : Les 12 couleurs de l'eau ne sont recalculees que lorsque les reglages de palette ou l'ancre changent, et le bruit de Perlin pour les arbres n'est appele que lorsque leur tranche de profondeur se renouvelle.
- **Clamps capacitaires** : Protection explicite de tous les compteurs d'instances (`InstancedMesh`) pour eviter les debordements de memoire GPU.

### B. Coalescence et caches paresseux (`src/runtime.ts`, `src/ui.tsx`)
- **Notifications rAF batchees** : Les evenements MIDI sont immediatement et synchroniquement routes vers le port physique et inscrits dans le journal, mais la notification des composants React (`notify()`) est coalescee une fois par frame via `requestAnimationFrame`. Pour les actions directes de l'utilisateur (Panic, arret d'enregistrement, changement de mode, deplacement de curseur), `notifyNow()` garantit une reactivite instantanee.
- **Cache par version de mutation** : Les getters couteux `notes`, `next` et `harmony` sont memoises et invalides uniquement lorsque `dataVersion` change.
- **Extraction et memoisation de `Key` (`React.memo`)** : Le piano virtuel pre-calcule un `Set` des notes actives en $O(1)$ et delegue chaque touche a un composant memoise qui ne re-rend que si son statut (actif, fantome, autorise) change.

### C. Theorie musicale enrichie et accords stabilises (`src/music.ts`)
- **Intervalles composes (13 a 24 demi-tons)** : Le rayon melodique configurable jusqu'a 24 demi-tons affichait auparavant des codes Tonal bruts (`15P`, `9M`). Ils sont desormais traduits en francais pur : Neuvieme majeure, Onzieme juste, Double octave, avec indication du sens (ascendant / descendant).
- **Fenetre de stabilisation d'accord (40 ms)** : Lors du jeu en arpege, le nom de l'accord ne clignote plus au passage de chaque note transitoire ; le moteur attend que le jeu se pose 40 ms pour emettre l'accord stabilise.

### D. Stockage persistant et file `in-flight + latest` (`src/session.ts`, `src/runtime.ts`)
- **Singleton IndexedDB** : La connexion a la base locale `rainbowjourney` reste ouverte et partagee via une promesse singleton reutilisee.
- **Coalescence des reglages** : Les deplacements successifs de curseurs sous 150 ms sans note MIDI intermediaire remplacent le dernier snapshot au lieu de gonfler le journal de centaines d'entrees redondantes.
- **File de sauvegarde non bloquante** : Le mecanisme `in-flight + latest` garantit qu'une sauvegarde automatique n'empile jamais de promesses concurrentes tout en assurant que le dernier snapshot d'arret est toujours ecrit.

### E. Accessibilite complete et ecran de repli (`src/Editor.tsx`, `src/Sessions.tsx`, `src/main.tsx`)
- **Navigation au clavier complet** : La barre d'onglets de l'editeur implemente les roles ARIA `tablist`/`tabpanel` avec navigation fluide aux fleches directionnelles gauche/droite.
- **Touche Escape & Restauration du focus** : Les tiroirs et boites de dialogue se ferment proprement a la touche `Escape` et restituent le focus sur le bouton qui les a ouverts.
- **Piege de focus modal** : La bibliotheque des parcours capture les tabulations pour eviter de perdre le focus derriere la modale.
- **React ErrorBoundary** : En cas d'exception inattendue, l'application ne presente plus d'ecran blanc muet mais un ecran de recuperation en ASCII avec reinitialisation controlee.

---

## 3. Bilan de la verification et des metriques

A l'issue de ce lot, tous les garde-fous de qualite ont ete executes et valides :

- **Vitest** : 25 tests unitaires et d'integration (4 suites) — **100% verts** (contre 12 initialement).
- **Playwright E2E** : 8 scenarios complets sur navigateur Chromium reel avec GPU D3D11 — **100% verts** (dont 4 tests d'accessibilite clavier natifs).
- **TypeScript (`tsc -b`)** : 0 erreur.
- **ESLint** : 0 avertissement.
- **Production Build** : Compile et minifie sans erreur.
- **Securite** : 0 vulnerabilite signalee par `npm audit`.
- **CI** : Workflow GitHub Actions operationnel.

---

## 4. Prochaine etape : Le test en temps reel collaboratif

Avant de coder les fonctionnalites du **Lot 2** (modulation expressive des effets, historique de 4 attaques, stockage par blocs et projection des touches), la prochaine seance consistera a brancher le piano physique Bird DP1 pour une **session d'improvisation collaborative en direct**.

Nous mesurerons les sensations de jeu, la pertinence des suggestions et l'impact visuel des rubans pour calibrer ensemble les futurs developpements.