init docs #189

Merged
Nora merged 32 commits from feature/motion-halloween into develop 2026-09-12 21:12:39 +00:00
Owner
No description provided.
init docs
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 57s
CI / PHP - tests + static + lint (pull_request) Successful in 8m36s
CI / Renderer - tests + lint JS (pull_request) Successful in 7m43s
d835e26412
Nora added 10 commits 2026-09-12 16:26:57 +00:00
Neuvième valeur acceptée par `meta.motion`. Elle ne fait encore rien : ni
module de rampe, ni branche dans `frameSourceFor`, ni algo qui la demande.

Ce qu'elle est, et ce qui la distingue des quatre autres motions qui
recomputent l'algorithme par frame : `zoom` bouge la caméra, `phase` et
`pulse` un champ, `swirl` la géométrie — `pour` avance une HORLOGE. Ce
qu'elle révèle est l'accumulation qu'un flux d'ops cache.

T01 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
`pourPath` sort un `{ elapsed }` par frame, du plancher à 1. Rampe linéaire,
pure, et qui ne lit RIEN de l'œuvre — contrairement à `swirlPath` qui part de
`params.swirl` : l'horloge d'un clip ne dépend pas de ce qu'on filme.

La dernière valeur est écrite 1 en littéral, pas calculée : `floor + (1-floor)*1`
ne vaut pas exactement 1 en flottant, et un ulp sur `elapsed` se propage dans
`ceil(drops × elapsed)` — ça peut coûter une goutte, donc l'identité d'octets de
la dernière frame.

Pas de `stopping one step short` ici : `pour` est une montée, pas une boucle.

T02 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Deux compteurs cohabitent désormais, et c'est tout le sujet du commit : `drops`
reste la coulée ENTIÈRE et continue de normaliser la masse d'une goutte
(`dropMass`) et le nombre de couches (`layers`, donc `progress`, donc la rampe
de teinte) ; `fallen` ne dit que jusqu'où le clip en est.

Les recalculer sur `fallen` aurait donné, à mi-course, une pile entière en
miniature et dans la mauvaise teinte, au lieu d'un instant de la vraie. C'est
exactement ce que vérifie le test : à `elapsed = 0.5` il y a moins de strates,
mais la PREMIÈRE est identique au profil près.

La garde « la dernière goutte ferme toujours une strate » devient, gratuitement,
tout le remplissage de la vidéo : sur un flux tronqué c'est elle qui peint la
surface vive en bande à chaque frame.

`elapsed` rejoint `MOTION_KNOBS` dans le même commit, sans quoi la suite passe au
rouge dès que le knob entre dans DEFAULTS (`meta.params` grandit).

Estampe byte-identique : golden b8e9d425… vert sans retouche, ENGINE_VERSION
immobile, aucun paramSpec touché.

T03 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Une couche fermée par `elapsed` est de la cire vive : elle vient de tomber, elle
n'a pas eu le temps de couler. Et surtout elle ne doit consommer aucun `rng()` —
sinon ses propres coulures changent de nervure d'une frame à l'autre et la strate
du dessus clignote pendant que la pile monte.

Portée exacte du défaut, mesurée : les strates DÉJÀ closes occupent les mêmes
positions de flux d'une frame à l'autre, donc elles étaient stables de toute
façon. Seule la bande vive était touchée. Réel et laid, mais pas un
bouillonnement général — la fiche a été corrigée pour ne pas surestimer l'enjeu.

Le RED a dû être refait : la paire 305/306 annoncée par la fiche ne discrimine
RIEN (les deux ferment une strate vive, l'écart vaut 3 des deux côtés, le test
passe avant le code). Il faut deux bornes dont le NOMBRE de clôtures diffère —
300 tombe sur une vraie frontière, 305 coupe en plein milieu d'une couche.

À `elapsed = 1`, `fallen === drops` : `living` est toujours faux, la clôture
forcée de la dernière goutte garde ses coulures, golden inchangé.

T04 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
On coupe l'INTÉGRATION du film (`steps`), jamais son débit : le fil est saisi en
pleine course — front pas encore arrivé, bourrelet en train de se former — au
lieu d'être court et gras. Mesuré sur la première coulure : 1,69 px d'étendue à
`elapsed = 0,06` contre 3,26 figée.

L'accord `RIPEN_SPAN = 1 - POUR_SPAN` est le point qui fait tenir le dispositif,
et personne ne le redérivera seul : il place la maturité de la DERNIÈRE strate
pile à la fin du clip. Du coup `elapsed = 1` redonne l'estampe sans le moindre
`if`, et surtout sans saut sur la dernière frame — vérifié à 0,999, toutes les
coulures à moins de 1 % de leur longueur finale.

Deux pièges de test rencontrés, consignés dans la fiche :

- mesurer à 0,2/0,4/0,6 ne prouve RIEN : la première coulure est figée dès 0,19
  (sa strate ferme à 0,0425, plus RIPEN_SPAN). Il faut calculer la fenêtre de
  mûrissement et mesurer dedans.
- `points[0].x` n'est pas l'ancrage : la silhouette commence par son bord gauche,
  donc à `x - demi-largeur`, et cette demi-largeur est lue sur l'épaisseur du
  film — elle bouge avec la maturité. L'ancrage stable est le milieu du premier
  et du dernier point.

Et une non-loi, notée dans le test : l'étendue n'est PAS monotone sur toute la
fenêtre (3,87 vers 0,10 puis retour à 3,26). Le front avance, puis la pression
capillaire rappelle la matière quand la traîne perd sa mobilité. C'est la
physique du film mince.

T05 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Mesures, protocole T44, 1557x1920 (taille de tournage depuis le fond perdu),
12 cœurs, budget 315 ms/frame :

  défauts             38,3 ms de moyenne  (12 % du budget)
  coin du paramSpec  246,2 ms             (78 %)

Le coin passe, et mieux que l'estimation du plan (309 ms), pour une raison
imprévue : le mûrissement des coulures rend les frames du début et du milieu
moins chères — `ceil(420 × ripeness)` pas au lieu de 420, et c'est l'intégration
du film mince qui domine. La motion se paie en partie elle-même.

Jurisprudence : pentaflake accepté à 242-245 ms (77 %), kaleido-fold refusé à
329,6 (105 %). 246 est du côté accepté.

Comptage de la distribution : AUCUN dump de prod disponible (base locale vide,
0 œuvre) — le comptage demandé n'a donc pas été fait, et la fiche le dit. À la
place, 40 tirages uniformes et indépendants knob par knob, c'est-à-dire la loi
exacte de drawValue() : 0/40 au-dessus du budget, médiane 45, p90 118, pire cas
151 ms. Le coin exige les quatre maxima simultanément, ce que quatre tirages
indépendants ne produisent pas.

Planche de contact : la cire monte franchement et sans à-coup, les coulures
croissent en nombre et en longueur. POUR_SPAN = 0,85 gardé. POUR_FLOOR = 0,06
gardé — la réserve du plan était juste (frame 0 maigre à layerEvery 60) mais
relever le plancher ferait perdre le début du remplissage, qui est le sujet.

T06 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Branche dans `frameSourceFor` — la seule des huit qui ne lise NI `params` NI
`canonicalHash` : l'horloge d'un clip ne dépend pas de l'œuvre filmée, elle part
toujours du plancher et finit sur l'estampe. Rien à faire remonter depuis `meta`,
pas d'équivalent de `swirlTurns`.

Rien à changer dans `pipeline-video.js` : `recomputeFrameFor` est déjà bâti une
fois pour tous les formats et applique `meta.videoParams`. Seul le commentaire
cite désormais `pour`.

`meta.motion = 'pour'` sur wax-crypt, et le test de motion bascule de 'draw-on' à
'pour' dans le même commit — il assert la clé, il ne peut pas partir avant elle
(scorie de découpage, notée dans la fiche T06).

Le pipeline carrousel éditorial partage `frameSourceFor` : il hérite de la branche
sans rien dupliquer.

T07 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
450 frames à la résolution de prod (1080x1920, ffmpeg réel, musique muxée) en
44,4 s, soit 98,7 ms/frame — dont 38,3 de recompute (banc T06) et ~60 de
letterbox + bandeaux + encodage h264. Cohérent, sans surprise.

À l'œil : la cire monte en continu, les strates posées ne bougent plus, les
coulures s'accumulent sur leurs nervures. La bande vive ne se trahit pas (elle
porte son arête claire comme les autres). Frame 0 habitée. Bandeaux sans conflit.

La dernière frame EST l'estampe : écart moyen 1,26/255 au raster canonique
recomposé, 0,023 % des octets au-delà de 16/255 — de la quantification h264.

Et pas de saut : f448→f449 vaut 0,22/255, du même ordre qu'une transition
courante (~0,10/frame). C'est l'accord RIPEN_SPAN = 1 - POUR_SPAN qui donne ça,
la dernière strate mûrit en continu.

Une réserve écrite, non mesurée : `fillRatio = 0,85` n'a pas été essayé. Les
bandeaux vivent dans les marges de letterbox, donc le risque est théorique, mais
aucune garantie n'est donnée.

`gen-video-local.mjs` n'a pas servi tel quel : son `fibonacciParams` en dur porte
un `colorMode` qui aurait changé l'œuvre filmée sans prévenir, et ses dimensions
sont l'ancien format.

T08 de docs/taches-motion-pour-wax-crypt/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Les trois fichiers en lockstep (version.js, VideoFormatCatalog.php, son test),
sans quoi pipeline-guard-video lève un RangeError -> 422 au premier rendu.

Le journal de version.js porte ce que personne ne redérivera seul :

- pourquoi draw-on ne pouvait pas (600 des 776 ops sont des nappes à alpha
  0,144 enfouies sous leur strate — 1,72 op/frame, 20 événements en 15 s) ;
- l'accord POUR_SPAN / RIPEN_SPAN = 1 - POUR_SPAN, qui place la maturité de la
  dernière strate pile à la fin du clip : `elapsed = 1` retombe sur l'estampe
  sans cas particulier ET sans saut sur la dernière frame ;
- l'invariant de la clôture vive (aucun rng consommé), et le fait que comparer
  les VALEURS de rng entre deux frames ne prouve rien ;
- les chiffres du banc, et le fait qu'aucun dump de prod n'était disponible.

Suites : phpunit vert, phpstan niveau 9 OK, mago lint + fmt OK (binaire extrait,
le wrapper composer ne peut pas écrire son lock ici). `npm test` : 2 échecs
seulement, les deux dans pipeline-slide.test.js — la dérive SIMD libvips déjà
connue, et ce fichier n'importe que sharp.

Estampe byte-identique : golden wax-crypt vert sans retouche, ENGINE_VERSION
immobile, aucun paramSpec touché. Donc au déploiement : rebuild de l'image
renderer, et rien d'autre — pas de catalog:sync, pas de re-rendu, pas de
migration.

T09 de docs/taches-motion-pour-wax-crypt/ — les 9 fiches sont jouées.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
docs: wax-crypt filme en pour, la tranche est livrée
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m8s
CI / PHP - tests + static + lint (pull_request) Successful in 6m8s
CI / Renderer - tests + lint JS (pull_request) Successful in 7m14s
520011a21c
Plan passé en LIVRÉ (9/9 fiches, moteur vidéo 1.20.0) et doc de référence de la
collection mis à jour avec les chiffres du banc plutôt qu'avec les estimations.

Deux corrections de fond par rapport à ce que le plan annonçait :

- le coût au coin du paramSpec est 246 ms, pas 305 — le mûrissement des coulures
  rend les frames du début et du milieu moins chères, ce qui n'avait pas été
  prévu ;
- sur 40 tirages uniformes du paramSpec, AUCUN ne dépasse le budget (pire cas
  151 ms). Le coin exigeait les quatre maxima simultanément.

blood-drip reste le candidat suivant, et son plan est maintenant moins cher à
écrire : la constante, le module de rampe et la branche de `frameSourceFor` sont
livrés, il ne reste qu'à lui faire lire `elapsed`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Nora added 10 commits 2026-09-12 16:41:29 +00:00
Le nombre de coulées DÉRIVE ici : `drips = matter / stickThreshold` avec
`matter = SEEP_MATTER × dripSteps / STEPS_REFERENCE`. Tronquer `dripSteps`
redécouperait donc la matière et changerait QUELLES coulées existent, au lieu de
dire combien sont déjà tombées. C'est la boucle de lâchers qu'il faut borner.

Le test le verrouille en deux assertions : moins de coulées à mi-course, mais la
PREMIÈRE identique au point près.

Échoue parce que `elapsed` n'est lu nulle part : les deux flux sont identiques.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Deux compteurs cohabitent : `drips` est le suintement entier — c'est lui que
l'adhérence a découpé dans la matière, donc lui qui dit QUELLES coulées existent
— et `fallen` dit seulement combien sont déjà tombées.

Borner `dripSteps` à la place aurait redécoupé la matière : on n'aurait pas eu le
même mur à mi-course, on en aurait eu un autre.

`elapsed` était déjà dans MOTION_KNOBS depuis la tranche wax-crypt, rien à y
ajouter. Golden c9e20084… vert sans retouche, ENGINE_VERSION immobile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Zéro changement de comportement. Le module décrivait ses deux lois (stick-slip
et film mince) et ses trois choix de composition, mais rien ne disait ce que
`elapsed` veut dire ni pourquoi il y a deux compteurs.

Le piège à ne pas laisser deviner : borner `dripSteps` redécouperait la matière
au lieu de dire combien de coulées sont tombées.

Tests toujours verts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
On coupera l'intégration du film (`steps`), jamais la charge lâchée : le filet
doit être saisi en pleine course — front pas encore arrivé, renflement en train
de se former — au lieu d'être court et gras.

Deux choix de mesure qui évitent les pièges rencontrés sur wax-crypt :

- `cellsOf` (déjà dans le fichier) lit le front atteint par le film depuis le
  nombre de points du polygone, donc la longueur de course sans dépendre d'aucune
  longueur peinte ;
- l'ancrage est le MILIEU du premier et du dernier point, pas `points[0].x` : la
  silhouette commence par son bord gauche, dont la demi-largeur est lue sur
  l'épaisseur du film et bougerait donc avec la maturité.

Bornes basses (0,03 / 0,06 / 0,09) parce que la fenêtre de la première coulée est
calculable et courte : elle tombe à `(1/drips) × POUR_SPAN` et a fini de courir
`RIPEN_SPAN` plus tard. Mesurer à 0,5 ne prouverait rien.

Échoue parce que `runThinFilm` reçoit ses FILM_STEPS = 500 pas quel que soit
`elapsed` : les trois fronts sont égaux.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
`ripeness` enfilé de `compute` → `runOf` → `flowOf` → `steps`. On coupe
l'INTÉGRATION du film, jamais la charge lâchée (`seed` garde son `load`) : le
filet est saisi en pleine course. Nourrir moins donnerait une coulée courte et
grasse, pas une coulée en train de courir.

Courbe mesurée sur la première coulée : 6 → 10 → 11 → 13 → 14 cellules de front
entre `elapsed` 0,01 et 0,09, puis saturation. Monotone, contrairement à
wax-crypt dont le bourrelet se retracte en figeant — la différence est le régime,
blood-drip lâche une charge d'un coup là où la bougie verse en continu.

Les deux constantes d'horloge sont posées avec leur accord :
POUR_SPAN = 0,7 et RIPEN_SPAN = (1 - POUR_SPAN)/2. La division par deux est
propre à cet algo : une coulée met RIPEN_SPAN à courir, PUIS son ruisselet met
encore RIPEN_SPAN — la perle du front ne lâche qu'une fois la mère arrivée.
wax-crypt, sans descendance, se contentait de RIPEN_SPAN = 1 - POUR_SPAN.

Le ruisselet hérite pour l'instant du `ripeness` de sa mère : c'est le cycle
suivant qui lui donne son horloge décalée.

Aucun helper extrait : un seul appelant pour l'instant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Il repart du BOUT de sa mère : la perle du front a fini par lâcher à son tour. Il
ne peut donc pas courir pendant qu'elle court encore. Sans décalage il hérite de
son `ripeness`, donc il existe dès qu'elle existe, et son point de départ
(`y + front × cellPx`) GLISSE le long de la paroi pendant qu'elle s'allonge.

`childProb: 1` donne un ruisselet à chaque coulée, donc un flux qui alterne mère
/ ruisselet : `runs[1]` est le premier ruisselet de façon fiable. Le défaut
(0,02) n'en donne qu'un par œuvre, impossible à cibler.

Deuxième test, qui passe DÉJÀ et devra rester vert : rien ne surgit sur la
dernière frame, ruisselets compris. C'est lui qui garde l'accord
POUR_SPAN + 2 × RIPEN_SPAN = 1 — avec l'accord de wax-crypt
(RIPEN_SPAN = 1 - POUR_SPAN, pas de descendance), le ruisselet de la dernière
coulée manquerait dans l'estampe.

Échoue : le ruisselet est à 16 cellules alors qu'il finira à 16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Correction du RED précédent, qui visait `runs[1]` : le flux n'alterne PAS mère /
ruisselet. 22 des 77 coulées par défaut meurent contre le bas du cadre
(`wall < FILM_CELLS_MIN`, donc `tip: null`) et n'engendrent rien — un index fixe
ne désigne donc rien de fiable, et le test mesurait une mère en croyant mesurer
un ruisselet.

Le seul repère fiable est géométrique : un ruisselet démarre EXACTEMENT au bout
de sa mère, serpentement compris. `axisAt(run, k)` lit l'axe du filet à la
cellule k (milieu des deux bords, le droit étant à l'index -1-k puisque la
silhouette referme le bord droit en sens inverse), et `motherChildPairs` apparie
sur l'égalité ancrage / bout.

Ni l'index, ni la grosseur ne distinguent les deux.

Bornes choisies sur mesure : à 0,17 la première mère appairée est figée (front 16
constant dès 0,17) alors que son ruisselet ne lâche qu'à 0,168 — il passe de 6 à
10 cellules entre 0,17 et 0,22.

Échoue : le ruisselet est à 10 cellules alors qu'il finira à 10 (il hérite du
`ripeness` de sa mère, donc il est déjà arrivé).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Il lâche quand la perle du front de sa mère lâche, donc une fois la mère arrivée.
Hériter de son `ripeness` le faisait exister avant elle — et comme il part de
`run.tip`, qui avance avec le front de la mère, il GLISSAIT le long de la paroi
au fil du clip au lieu de partir d'un point fixe.

`ripenessAt(elapsed, fellAt)` extrait maintenant qu'il y a un SECOND appelant :
la mère à `fellAt`, le ruisselet à `fellAt + RIPEN_SPAN`. Pas avant — une
abstraction par besoin réel.

Mesuré : le premier ruisselet appairé passe de 6 à 10 cellules de front entre
`elapsed` 0,17 et 0,22, sa mère étant figée à 16 dès 0,17.

C'est ce décalage qui justifie RIPEN_SPAN = (1 - POUR_SPAN)/2 plutôt que
l'accord de wax-crypt : la dernière coulée tombe à 0,70, fige à 0,85, et son
ruisselet à 1,00 pile. Le test de continuité (rien ne surgit sur la dernière
frame, ruisselets compris) le garde.

Golden c9e20084… vert sans retouche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Zéro changement de comportement, 31/31 toujours verts.

- `1e-9` → `SAME_POINT`, avec la raison : les coordonnées sortent du même calcul
  des deux côtés (`runOf` recompose le bout avec la formule que `rivulet` a
  peinte), donc l'écart attendu est zéro à l'arrondi près ;
- les bornes de mesure → `RUN_RUNNING_AT` / `RUN_YOUNG_AT` / `CHILD_RUNNING_AT`,
  chacune avec le calcul de sa fenêtre en commentaire plutôt que trois littéraux
  répétés dans deux tests ;
- `axisAt` remonte aux helpers partagés du fichier : il servait dans un test
  déclaré avant sa définition (légal — le callback court après l'évaluation du
  module — mais illisible), et il remplace le calcul d'ancrage recopié à la main
  dans le test de non-dérive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
remove fils
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m18s
CI / PHP - tests + static + lint (pull_request) Successful in 5m46s
CI / Renderer - tests + lint JS (pull_request) Successful in 6m47s
f158cc1719
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
docs(video): wipe n'a plus de client, dire lequel et pourquoi
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m55s
CI / PHP - tests + static + lint (pull_request) Successful in 7m5s
CI / Renderer - tests + lint JS (pull_request) Successful in 7m52s
6c0b1436ab
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
fix(dev): le rendu vidéo local tourne à la taille de génération du fond perdu
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m23s
CI / PHP - tests + static + lint (pull_request) Successful in 6m36s
CI / Renderer - tests + lint JS (pull_request) Successful in 7m34s
0c1ee95c1e
3425 x 4429 était la taille d'avant le tirage à fond perdu : la taille de
tournage qui en sortait (1485 x 1920) n'était plus celle de la prod
(1557 x 1920), donc tout banc local mesurait 4,6 % trop peu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
fix(blood-drip): une coulée existe pile quand sa lèvre cède
Some checks failed
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m34s
CI / PHP - tests + static + lint (pull_request) Failing after 4m28s
CI / Renderer - tests + lint JS (pull_request) Successful in 6m27s
723172063b
Deux horloges gouvernaient une coulée sans s'accorder : celle qui la fait
EXISTER (`fallen`, lu sur `elapsed` nu) et celle qui la fait COURIR
(`fellAt`, lu sur `elapsed × POUR_SPAN`). La première avançait 1/0,7 fois
plus vite que la seconde, donc une coulée naissait avec sa course déjà
faite — et d'autant plus faite qu'on était tard dans le clip. Au-delà de
la mi-clip elle surgissait ENTIÈRE : la seconde moitié du reel n'était
plus un mur qui suinte mais un mur qu'on couvre de traits posés.

`fallen` se lit désormais sur l'horloge du suintement, la même que
`fellAt`. Chaque coulée part à longueur de goutte et dispose de tout son
`RIPEN_SPAN` pour courir, la dernière comprise.

Mesuré : une coulée du dernier tiers naissait à 11 cellules sur les 13,7
d'une coulée figée moyenne, elle naît maintenant à 6 — la taille de la
charge lâchée, celle de la toute première coulée.

L'accord POUR_SPAN + 2 × RIPEN_SPAN = 1 ne bouge pas, l'estampe reste
identique à l'octet (`fallen === drips` à `elapsed = 1` dans les deux
cas), aucun `rng()` ajouté ni déplacé.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Deux horloges gouvernaient une coulée sans s'accorder : celle qui la fait
EXISTER (`fallen`, lu sur `elapsed` nu) et celle qui la fait COURIR
(`fellAt`, lu sur `elapsed × POUR_SPAN`). La première avançait 1/0,7 fois
plus vite que la seconde, donc une coulée naissait avec sa course déjà
faite — et d'autant plus faite qu'on était tard dans le clip. Au-delà de
la mi-clip elle surgissait ENTIÈRE : la seconde moitié du reel n'était
plus un mur qui suinte mais un mur qu'on couvre de traits posés.

`fallen` se lit désormais sur l'horloge du suintement, la même que
`fellAt`. Chaque coulée part à longueur de goutte et dispose de tout son
`RIPEN_SPAN` pour courir, la dernière comprise.

Mesuré : une coulée du dernier tiers naissait à 11 cellules sur les 13,7
d'une coulée figée moyenne, elle naît maintenant à 6 — la taille de la
charge lâchée, celle de la toute première coulée.

L'accord POUR_SPAN + 2 × RIPEN_SPAN = 1 ne bouge pas, l'estampe reste
identique à l'octet (`fallen === drips` à `elapsed = 1` dans les deux
cas), aucun `rng()` ajouté ni déplacé.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Même désaccord d'horloges que sur `blood-drip` : `fallen` se lisait sur
`elapsed` nu alors que `closedAt` se lit sur `elapsed × POUR_SPAN`. Une
strate était donc peinte 1/0,85 fois trop tôt pour ses propres coulures,
et elle arrivait avec elles déjà pendues, terminées. La pile montait, mais
les fils ne coulaient pas.

`fallen` se lit désormais sur l'horloge de la veillée. Chaque coulure part
d'une épaisseur peignable et dispose de tout son `RIPEN_SPAN`.

Mesuré : une strate du dernier tiers naissait avec 91 % de l'étendue que
ses propres coulures atteindraient jamais, elle naît à 25 %.

Les deux tests qui épinglent l'invariant de troncature convertissent
maintenant un nombre de gouttes en instant de clip par cette horloge-là,
d'où l'export de `POUR_SPAN` : recopier une constante dans un test, c'est
la laisser dériver.

Accord `RIPEN_SPAN = 1 - POUR_SPAN` intact, estampe identique à l'octet
(`fallen === drops` à `elapsed = 1` dans les deux cas), aucun `rng()`
ajouté ni déplacé. Coût +8 % en moyenne, frame la plus chère inchangée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiB1vcz5CtmpKnDfnqZUoq
Merge remote-tracking branch 'origin/feature/motion-halloween' into feature/motion-halloween
All checks were successful
CI / Renderer - couverture JS (pull_request) Has been skipped
CI / Renderer - audit des dépendances JS (pull_request) Successful in 1m28s
CI / PHP - tests + static + lint (pull_request) Successful in 5m59s
CI / Renderer - tests + lint JS (pull_request) Successful in 7m6s
7d4e3d9d6b
Nora merged commit 79fa31c90a into develop 2026-09-12 21:12:39 +00:00
Nora deleted branch feature/motion-halloween 2026-09-12 21:12:40 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Nora/ArtAutomatic!189
No description provided.