Skip to content

Fix/courbe continuite visuelle - #344

Merged
bbstudioapp merged 65 commits into
developfrom
fix/courbe-continuite-visuelle
Aug 24, 2026
Merged

Fix/courbe continuite visuelle#344
bbstudioapp merged 65 commits into
developfrom
fix/courbe-continuite-visuelle

Conversation

@bbstudioapp

Copy link
Copy Markdown
Owner

No description provided.

…lapsed

État intermédiaire, ne compile PAS encore — cf. état de reprise dans
~/.claude/orchestration/etat-courbe-2026-08-21.md pour la suite.

Amorce du chantier continuité de la courbe de mouvement (retour terrain
21/08) : ajoute le gel de position (_frozenIdx/_frozenAt) pour le futur pont
synthétique de la courbe pendant une transition de step, et l'ancrage
elapsed (_elapsedAnchorAt/_elapsedAnchorValue) pour extrapoler en continu
entre deux ticks du SessionController au lieu d'utiliser la valeur figée.
_buildForMode appelle désormais _PositionLadder pour tous les modes (plus
plus de _Pulse/_StaticPosition/_Breath en top-level) mais le constructeur
de _PositionLadder n'a pas encore les nouveaux champs — ne compile pas.
…nce)

Fait compiler le commit wip précédent (68b7387) qui gelait la position
visuelle avant transition sans que _PositionLadder ne consomme encore
frozenIdx/frozenAt/pulseT.

- _PositionLadder gagne frozenIdx/frozenAt/pulseT ; le curseur passe par
  la nouvelle _CursorVisual (dispatch du pulse par mode), qui remplace
  _Pulse/_Breath/le pulse inline de _StaticPosition (3 classes mortes
  supprimées, orphelines depuis que _buildForMode ne switch plus dessus).
- _computeFutureBeats ne retourne plus [] quand lastBeatAt == null : pont
  synthétique de frozenIdx vers `to` sur _bridgeMs (260ms, partagée avec
  la durée de l'AnimatedAlign), puis reprise de la chaîne normale.
- Segment plat (hold/beg/suckle/biffle/breath/freestyle) : rattrapage
  direct de nextTime à now pour éviter des centaines d'itérations à vide
  sur un hold/biffle tenu longtemps (lastBeatAt n'est jamais rafraîchi
  hors rhythm/lick/hand).
- Alternance préservée à la frontière entre deux steps qui alternent
  tous les deux : vise `from` du step suivant au lieu de `to` si `to`
  continuerait dans le même sens que le dernier point affiché.
- session.dart : commentaire suckle mis à jour (référençait la classe
  _StaticPosition supprimée).

flutter analyze : No issues found. Tests de géométrie pas encore écrits
(prochaine étape, cf. etat-courbe-2026-08-21.md).
_frozenIdx capturait oldWidget.from/to bruts (from BeepEngine) au lieu du
mapping ladder que _buildForMode affiche reellement (breath/biffle/freestyle
forces a tip/tip). Transition respiration -> gorge : l'ancre gelait une
position fantome (ex. throat residuel), jamais montree a l'ecran, et le pont
demarrait du mauvais point.

Extrait _ladderPositionsFor en methode statique partagee entre _buildForMode,
initState, didUpdateWidget et la resolution des frontieres upcomingSteps dans
_computeFutureBeats (la prevision d'un futur step breath/biffle/freestyle
doit aussi rester plate a tip, pas alterner sur ses from/to bruts).
_PositionLadder pilotait un AnimatedAlign separe pour le curseur en plus de
_computeFutureBeats qui recalcule a la main la meme position pour tracer la
courbe. Les deux moteurs d'interpolation independants (horloges/instants
now() distincts, retrigger de la Tween interne de Flutter) pouvaient diverger
frame a frame, surtout au moment d'une transition.

Le curseur lit desormais directement beats.first.idx (le point d'ancrage
t=0, isAnchor:true) au lieu de piloter sa propre AnimatedAlign : une seule
source pour "ou est le curseur maintenant", plus de risque de divergence par
construction. _toAlign accepte un num pour le idx fractionnaire de l'ancre.
La prevision visait `from` a une frontiere de step pour preserver
l'alternance visuelle, alors que `BeepEngine` joue toujours `to` en
premier. Le recalage sur le vrai BeatEvent deplacait donc toute la
courbe d'un coup a chaque transition.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Le premier bip d'un step tombait toujours sur `to`, ce qui pouvait
enchainer deux mouvements dans le meme sens au passage d'un step au
suivant. `shouldStartOnTo` est pure et statique pour que l'affichage
predise la meme chose au lieu d'en ecrire une seconde version.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Le miroir local des deux dernieres positions emises permet a la courbe
d'appeler `BeepEngine.shouldStartOnTo`, la meme fonction que `applyStep`,
au lieu d'une seconde regle qui divergeait.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Les trois commits annules ont ete joues et rejetes : la prevision alignee
sur `to` ("pire qu'avant"), puis l'alternance portee par le moteur
("a l'oeil c'est horrible"). L'etat 86ec18d reste le meilleur retour
terrain. Les commits restent dans l'historique.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
…calculs

_PositionLadder recalculait _computeFutureBeats() a chaque frame (pulseT,
60 fps) au lieu de seulement quand la geometrie change reellement. Passage
en StatefulWidget : _PositionLadderState memoise le resultat brut + son
instant de calcul, ne recalcule que si (mode, from, to, beatDuration,
flipped, lastBeatAt, frozenIdx, frozenAt, rowCount, upcomingSteps) change,
et fait glisser les points memoises entre deux calculs. Le curseur devient
la valeur de la courbe decalee interpolee a t=0 (meme easing que le pont
synthetique), au lieu de beats.first.idx brut.

flutter analyze propre, suite existante verte. Tests de geometrie a
ecrire dans un prochain commit.
Aucun test ne couvrait le chantier de memoisation (movement_animation.dart).
Ajoute test/movement_trajectory_scroll_test.dart via 2 sondes @VisibleForTesting :
- scrollBeatsForTest expose _scrollBeats : entre deux calculs les points
  futurs gardent leur idx et glissent de deltaT, un point sorti a gauche
  disparait, le nouvel ancrage a t=0 interpole selon Curves.easeInOutCubic
  (pas lineaire), et le scroll renvoie null quand la memoisation est caduque.
- sameGeometryForTest expose _sameGeometry (construit 2 _PositionLadder et
  compare via la fonction reellement utilisee par le State, pas une copie) :
  des parametres identiques (pulseT hors cle) ne declenchent aucun recalcul,
  un lastBeatAt neuf en declenche un, une nouvelle instance de liste
  upcomingSteps a contenu identique n'en declenche pas.

Chacune des 6 assertions verifiee au sabotage manuel (code d'avant / valeur
par defaut renversee / regle neutralisee) : le rouge tombe sur le bon test
a chaque fois, sans effet de bord sur les autres.
La prevision visait `from` a une frontiere quand viser `to` aurait
enchaine deux mouvements dans le meme sens. Le verdict dependait des
deux derniers points calcules : selon l'instant du calcul, la courbe
basculait d'une geometrie a l'autre. Le moteur, lui, joue toujours `to`.

Etape 2 de specs/timeline_source_unique.md, rejouee seule par-dessus
l'etape 1 comme la spec l'exige.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Le defilement decale les points sans en fabriquer : la fenetre se vidait
jusqu'a n'avoir plus rien, puis la courbe reapparaissait par blocs au
recalcul suivant. Regression introduite par la memoisation de l'etape 1.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Le rattrapage repartait de l'instant present : deux recalculs successifs
posaient leurs points a des instants differents, et le plateau sautait.
En avancant par multiples entiers du battement, un recalcul redevient
invisible.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Applique a chaque point, l'easing faisait ralentir le curseur jusqu'a
l'arret entre deux points alignes : une saccade a chaque mini-point.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Les deux sondes affirmaient l'easing sur chaque point : elles decrivaient
le comportement juge sacade. Ajout du trajet continu (lineaire) et de
l'arrivee sur plateau (amorti) — cette derniere rend la regle
discriminante, la mutation `outgoing == -incoming` la fait tomber.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Generalise le repere de frontiere a tous les modes, et classe beg du cote
bouche.

Claude-Session: https://claude.ai/code/session_016PDTJjnBAbqpgNUDwWZjzC
Relecture du chantier fix/courbe-continuite-visuelle (develop..HEAD, 24
commits). Verdict : publiable avec réserves. Un défaut réel identifié par
lecture de code (dérive de l'ancre elapsed pendant un défi intra-séance)
a été corrigé de façon concurrente par le commit 0b618a5, arrivé pendant
cette relecture — correctif vérifié sain. Réserves restantes : aucun test
ne monte MovementAnimation, rowCount=6/hand non couverts par une sonde
dédiée.

Claude-Session: https://claude.ai/code/session_01FMUv18rYdq5dpfMt1gwXhk
Huit steps ecrits a la main posaient from == to en rhythm/lick : le moteur
y relevait `from`, la courbe annoncait un plateau. `tip` etant la seule
position plus aigue que `head`, le son joue exactement pareil.

Les deux emplacements a plusieurs candidats (rapid_full, demo t=210) sont
laisses tels quels : les figer changerait ce qui est entendu.

Claude-Session: https://claude.ai/code/session_01MdwmT41x1W4p1hxbnhxU2r
…estrateur

Publiable avec reserves. Les trois affirmations centrales resistent aux
tentatives de refutation (mutation stepSerial rejouee rouge, son inchange sur
les 8 sites tip->head, commentaire isTimelineFrozen verifie vrai dans les deux
sens). Deux angles morts trouves dans la sonde de contenu (collision de cle
path#time, filtre de mode qui ignore defaultMode) et un timer BeepEngine
jamais arrete dans la sonde stepSerial - sans consequence observee aujourd'hui.

Claude-Session: https://claude.ai/code/session_01ANQRErewn1pWef3BNQff7z
…on time

Deux steps de milestones differentes a time=10 fusionnaient dans le Set :
la sonde voyait 5 sites la ou 6 existaient. Le mode herite du document est
desormais resolu, au lieu d'etre suppose rhythm.

Trouve par la relecture adverse de 52893b5.

Claude-Session: https://claude.ai/code/session_01MdwmT41x1W4p1hxbnhxU2r
La courbe et le curseur restent, les pastilles par beat passent derriere
un toggle de l'ecran SONS (DebugSettingsService), aligne par defaut sur
kDebugMode. Aucun calcul de trajectoire touche : seul leur dessin est
conditionne.

Claude-Session: https://claude.ai/code/session_01DgLgSc6tAj25QHSZZ8qFwZ
…rajectoire

Relecture adverse du commit 67aa2c3 (étape 6 de la spec timeline) : le
défaut ?? kDebugMode de DebugSettingsService.getShowTrajectoryDots()
était le seul du service à ne pas être un littéral fixe, et rien ne le
verrouillait. Sonde rouge sur ?? false avant restauration du code.

Claude-Session: https://claude.ai/code/session_014tMhzZ2sED1QJZSU2oieSa
…publiable avec reserves

Périmètre : commit 67aa2c3. Aucun bug fonctionnel trouvé (défaut
kDebugMode correctement appliqué, courbe et curseur intacts dans les
deux réglages, 5 mutations rejouées et rouges sur la bonne assertion,
traductions complètes). Réserves : couverture de test seulement — cf.
commit 3f80a9c pour le défaut kDebugMode corrigé, et l'inventaire des
2 maillons de câblage session_screen.dart non gardés (coût jugé
disproportionné pour cette relecture).

Claude-Session: https://claude.ai/code/session_014tMhzZ2sED1QJZSU2oieSa
L'étape 5 de la spec timeline supposait qu'un step stable ne posait qu'un
point figé. Mesure : la boucle en pose déjà une série à l'intervalle
`_durationFor(mode, bpm)` — la valeur candidate de la spec — et `44ac50b`
l'a rendue stable entre deux recalculs sans laisser de test derrière lui.

Trois sondes prouvées rouges par mutation : la série (break sur segment
plat), sa grille (retour à `nextTime = now`), et le câblage du mode vers
`beatDuration`.

Claude-Session: https://claude.ai/code/session_01HMXdydpYmwvDkRbWRTeawN
…ajectoire

Aucun chemin ne le lisait : `to` est reaffecte depuis `resolveStepConfig`
au debut de chaque iteration, avant toute lecture. Deux tests du groupe
`resolveUpcomingMovementSteps` le montraient deja en verifiant que `to`
ne s'herite jamais.

Claude-Session: https://claude.ai/code/session_01GEBPmVvCvSY7bA1rvFtwhH
…onde

Deux cibles de la spec ne sont pas mortes : le resolveur a un appelant de
production, et les variables d'alternance avaient deja disparu avec 44df1e1.
Seul currentTo l'etait. Releve joint : 23 fix de la branche, dont le cablage
de la garde de gel qu'aucun test ne tient.

Claude-Session: https://claude.ai/code/session_01GEBPmVvCvSY7bA1rvFtwhH
…on, publiable avec reserves

Consigne « chercher a refuter, pas a valider ». Retrait de currentTo confirme
structurellement et par execution (36 combinaisons rejouees, resultats
identiques). Les trois mutations des sondes de plateau tombent rouges pour
la bonne raison. Sondage par mutation de 3 entrees du releve des 23 fix :
un « garde » confirme rouge, le duo a02694e/22a6cd8 confirme non couvert
(suite complete verte apres retrait du ternaire de garde). Aucun defaut
trouve, rien corrige.

Claude-Session: https://claude.ai/code/session_012hKHh1UV2V8GNhucaky2Nc
…jectoire

Un recalcul tombe entre la frontiere annoncee et l'application du step
reposait le curseur sur une corde partant du bip synthetique de fin de
pont, vieux de toute la tenue. Le dernier repere deja passe devient
l'origine de l'ancre : le recalcul ne deplace plus le curseur.

Claude-Session: https://claude.ai/code/session_01QJH6eqJaCYcq8rg7xfRPaz
… est tronquee

Le point d'arrivee du pont n'est pas pose quand il tombe a moins d'une
milliseconde : sans origine fraiche, le curseur retombait a 2,693 au lieu
de rester sur full. La sonde balaie la fenetre de troncature.

Claude-Session: https://claude.ai/code/session_01QJH6eqJaCYcq8rg7xfRPaz
Mise en forme du commit a41dc29 : la declaration du dernier repere passe
s'etait glissee entre le commentaire de l'arrivee du pont et son `if`.

Claude-Session: https://claude.ai/code/session_01QJH6eqJaCYcq8rg7xfRPaz
…de tenue

Rejeu independant des deux sondes (rouge/vert identiques a la decimale pres,
20/20 dans les deux sens), recherche infructueuse d'un cas ou la nouvelle
origine degrade le curseur, verification par lecture de code du "saut de
deux rangees" attribue a un artefact de sonde, replay independant de la
fiche sas tss2-007. Verdict : publiable avec reserves.

Claude-Session: https://claude.ai/code/session_01GyqQGuEP1grBbCfThgChAT
…gel d'horloge

Verdict publiable : rouge confirme sur la bonne assertion (30 lancements),
regression exacte du 21/08 rejouee, compteur de controle et fidelite du
harnais verifies par mutation. Nuance la revendication de "transitivite"
sur la 3e branche du gel : tenue au niveau du site d'appel, pas au niveau
du contenu du getter (demontre par mutation ciblee).
… posture

Troisieme branche de `isTimelineFrozen`. Retirer `|| awaitingPostureReady`
de l'agregat laissait les 1100 tests verts : la sortie d'un break qui impose
une posture amene le gel sans defi actif, et l'ecran est lu frame par frame.

Claude-Session: https://claude.ai/code/session_01EGSCeDdrv3nRCx2LUT82vA
…nte de posture

Rejeu du trou avant de l'outiller : la mutation annoncee par la relecture du
22/08 casse trois tests du getter, celle qui isole le cablage laisse les 1100
verts. Reformule au passage la phrase « tenue par transitivite » du document
du 22/08 : vrai du cablage, faux du comportement.

Claude-Session: https://claude.ai/code/session_01EGSCeDdrv3nRCx2LUT82vA
…gel d'horloge pendant une attente de posture

La contradiction annoncee entre les relectures du 22/08 et du 23/08 n'en
est pas une : les deux mesures sont exactes, chacune pour le perimetre
qu'elle annonce (fichier unique vs suite complete ; avant vs apres
l'ecriture du nouveau test). Rejoue independamment via trois agents en
worktree isole : mutations, robustesse de la sonde, determinisme et cout.
Corrige au passage un commentaire du nouveau test qui pretait au faux
moteur TTS un effet qu'il n'a pas dans ce scenario (aucun step n'a de
texte, pas de PhraseBank fournie -> _tts.speak() n'est jamais appele).

Claude-Session: https://claude.ai/code/session_0152ffezQKzVtroRDNWYRRJs
Les deux premiers commits du chantier portaient le type "wip", refuse par
la verification des messages de commit. Les renommer en perf/fix a change
l'identifiant des 64 commits de la branche : les 184 renvois cites dans
les rapports d'analyse pointent de nouveau vers les bons commits.

Claude-Session: https://claude.ai/code/session_01Donp597cyArWruCwDeCy2j
@bbstudioapp
bbstudioapp force-pushed the fix/courbe-continuite-visuelle branch from 61fd818 to a8f2986 Compare August 24, 2026 19:23
@bbstudioapp
bbstudioapp merged commit c57bdcd into develop Aug 24, 2026
5 checks passed
@bbstudioapp
bbstudioapp deleted the fix/courbe-continuite-visuelle branch August 24, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant