Fix/courbe continuite visuelle - #344
Merged
Merged
Conversation
…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
…ecanisme reproduit dans _scrollBeats Claude-Session: https://claude.ai/code/session_015eEPoi8egiH85RBbHSJfzt
…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
…ion, preuve rouge 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
force-pushed
the
fix/courbe-continuite-visuelle
branch
from
August 24, 2026 19:23
61fd818 to
a8f2986
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.