From be7c74b9989940b15a6474578c461311b44c780d Mon Sep 17 00:00:00 2001 From: Antonino Cuzzola Date: Tue, 15 Sep 2026 11:59:07 +0200 Subject: [PATCH] Due banchi che non potevano fallire: uno ora boccia, l'altro esce dalla CI framelocked-verdict.sh stampava il verdetto ed usciva 0 comunque. Ora esce 1 su una divergenza e 3 se un render non esce, accetta le composition come argomenti e gira anche su PromptInput nei frame della digitazione (TXT-01). La CI lo prova su FrameLockedProbeRandom, la stessa sonda con un Math.random dentro, spostato fino a 400 px: a 40 Chrome allineava al pixel e due render coincidevano per caso. fill-measure.sh dava 20 su 20 anche su una lastra ridotta al 60% col fondale vero intorno: il fondale delle scene ha luminanza intorno a 20 e la soglia e' 2. Nemmeno la tessitura separa i due casi, per via del piano attenuato dietro la lastra. Resta come lettura, esce dalle misure generate dal catalogo, e CAM-01 va in grigio finche' il verdetto sul riempimento non sara' geometrico. La pagina conta i banchi dai propri dati invece di scriverlo a mano: 29 su 36 possono fallire in CI, 6 non sono scritti (CAM-01 e GIU-04 si aggiungono) e 1 esiste ma non gira in CI (CAM-03, legibility.sh vuole i film di riferimento). La tabella dei banchi e il README dicono lo stesso. La run di main durava 23:41 su un limite di 25 minuti: il limite passa a 40, col commento che la cura vera e' dividere il lavoro in job. Co-Authored-By: Claude Opus 5 --- .github/workflows/showcase.yml | 17 ++++++++- README.md | 42 +++++++++++++++----- scripts/catalog.mjs | 15 +++++--- scripts/fill-measure.sh | 21 ++++++++++ scripts/framelocked-verdict.sh | 49 +++++++++++++++++++----- showcase/grammatica.html | 55 ++++++++++++++++++++------- video/src/Root.tsx | 14 +++++++ video/src/scenes/FrameLockedProbe.tsx | 23 +++++++++-- video/src/scenes/catalog.json | 4 +- 9 files changed, 195 insertions(+), 45 deletions(-) diff --git a/.github/workflows/showcase.yml b/.github/workflows/showcase.yml index 0727eff..338b0b7 100644 --- a/.github/workflows/showcase.yml +++ b/.github/workflows/showcase.yml @@ -43,7 +43,12 @@ concurrency: jobs: showcase: runs-on: ubuntu-latest - timeout-minutes: 25 + # IL MARGINE ERA FINITO. La run completa di main del 13 settembre 2026 e' + # durata 23 minuti e 41 secondi contro un limite di 25: un solo banco in piu' + # l'avrebbe fatta scadere, e una run scaduta non pubblica niente. 40 e' il + # margine per i passi che vengono; la cura vera e' dividere il lavoro in job + # paralleli, non alzare ancora questo numero. + timeout-minutes: 40 steps: - uses: actions/checkout@v4 @@ -110,6 +115,9 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' run: | ./scripts/framelocked-verdict.sh + # E sulla scena che digita, nei frame della digitazione: e' li' che un + # caso non seminato (TXT-01) romperebbe la ripetibilita'. + FRAMES="150 175 200" ./scripts/framelocked-verdict.sh PromptInput node scripts/catalog.mjs measures > /tmp/misure.sh sh -e /tmp/misure.sh < /dev/null ./scripts/handoff-travel.sh @@ -144,6 +152,13 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' working-directory: . run: | + # framelocked-verdict.sh deve bocciare la sonda con Math.random dentro. + # Fino a settembre 2026 stampava il verdetto ed usciva 0 comunque, e + # una divergenza vera sarebbe passata col verde. + rc=0; ./scripts/framelocked-verdict.sh FrameLockedProbeRandom > /tmp/fv.log 2>&1 || rc=$? + [ "$rc" = 1 ] || { echo "framelocked-verdict.sh sulla sonda con Math.random: atteso 1, ottenuto $rc" >&2; cat /tmp/fv.log >&2; exit 1; } + echo "framelocked-verdict.sh boccia la sonda con Math.random, come deve." + ./scripts/fixture-screenshot.sh if ./scripts/focus-sharpness.sh video/out/.fixture-card-focus-screenshot.mp4 >/dev/null 2>&1; then echo "focus-sharpness.sh promuove una scena fatta di screenshot: non misura niente." >&2 diff --git a/README.md b/README.md index a5c7f52..8f55f83 100644 --- a/README.md +++ b/README.md @@ -407,9 +407,9 @@ npx remotion render PromptInput out/prompt-input.mp4 # from video/ ./scripts/contact-sheet.sh out/prompt-input-vs-ref.png # composition, frozen ./scripts/review-page.sh # composition + rhythm, moving ./scripts/beats.sh # are all four beats on screen -./scripts/fill-measure.sh video/out/prompt-input.mp4 # does it fill the frame +./scripts/fill-measure.sh video/out/prompt-input.mp4 # border readout only, it cannot fail (see below) ./scripts/legibility.sh # down to what size it reads -./scripts/framelocked-verdict.sh # is it really frame-locked +./scripts/framelocked-verdict.sh [Composition ...] # is it really frame-locked ./scripts/seam.sh [A.mp4] [B.mp4] # is the join really cutless ./scripts/handoff-travel.sh # does the card actually cross ./scripts/focus-sharpness.sh # does the text survive the push-in @@ -453,11 +453,33 @@ before send, the pace of the streaming response. A strip of frames cannot show any of that. `fill-measure.sh` and `legibility.sh` are the two defects that sank the first -scene, turned into numbers. Measured on both renders: the frame is full on all -four edges at every instant sampled (20 of 20 samples on `ui-mockup`), where the -reference keeps two of four alive and the retired `OrbitLoop` none. Text -survives downscaling at least as well as the reference, and better below 640px -wide. +scene, turned into numbers. Text survives downscaling at least as well as the +reference, and better below 640px wide. + +The fill number, though, stopped meaning anything, and for a while nobody +noticed because it kept saying 20 of 20. It counts a border strip as alive when +its mean luminance is above 2, which separated a slab from the black background +of the retired `OrbitLoop`. The scenes now sit on the app's own background, whose +luminance is around 20, so every strip is alive whatever is in it. Measured: the +first render of `ui-mockup` shrunk to 60 per cent, with the real background +colour around it, reads 20 of 20. Texture does not rescue it either: the ratio +between the local variance of a border strip and of the centre falls to 0.014 on +`card-release` with the slab in frame and rises to 0.043 on `board-orbit`, where +the borders show the background on purpose, because the attenuated plane behind +the slab is the board drawn a second time and any pixel measure reads it as +content. So the script is kept as a readout, it is out of the CI measurements, +and `CAM-01` on the catalogue page is grey. Whether a slab covers the frame is a +question for the geometry: project the slab at the sampled frames and require it +to cover all four edges. + +`framelocked-verdict.sh` had the opposite problem. It measured the right thing +and then exited 0 whatever it found, so a real divergence would have scrolled past +in a green log. It exits 1 now, CI checks that it fails `FrameLockedProbeRandom` +(the same probe with a `Math.random` inside), and it also runs on `PromptInput` +at frames 150, 175 and 200, in the middle of the typing, which is where an +unseeded random would break `TXT-01`. The random offset in that probe spans 400 +pixels and not 40: Chrome snaps the position to whole pixels, and at 40 two +renders landed on the same pixel on the first try. `OrbitLoop` is what `UIMockup` replaced, and it is named here only as the baseline the measurements are read against. It is no longer a composition. @@ -652,9 +674,11 @@ render that exists and a page that shows it cannot drift apart. Open playing (`UIMockup`, `CardHandoff`, `CardFocus`, `CardRelease`, `PromptInput`, `BoardOrbit`, in the order they join), the four rules, the -license note. Beside it, `showcase/grammatica.html` is the catalogue: twenty-six +license note. Beside it, `showcase/grammatica.html` is the catalogue: thirty-six movements with a live demo each, the numbers they start from, and the bench that -can fail them. The demos are browser re-creations of the slab, not the renders, +can fail them. Seven of those benches are printed in grey, because six have not +been written and one needs material that is not in git; the page counts them from +its own data, since the hand-written count said thirty-two when it was twenty-nine. The demos are browser re-creations of the slab, not the renders, which is the point of keeping them next to the renders rather than instead of them. Neither page carries a build step or a dependency, so what you open locally is what ships. diff --git a/scripts/catalog.mjs b/scripts/catalog.mjs index b96c93a..3dff143 100755 --- a/scripts/catalog.mjs +++ b/scripts/catalog.mjs @@ -32,18 +32,21 @@ const commands = { ids: () => scenes.forEach((s) => out(s.id)), - // I banchi che dipendono solo dal catalogo: il riempimento di ogni scena che - // lo dichiara, e la giunta di ogni coppia adiacente. Quelli specifici di una - // scena sola (focus-sharpness, handoff-travel) restano scritti nel workflow: - // non sono derivabili da un elenco, e fingere il contrario nasconderebbe che - // esistono. // Le scene che dichiarano di stare ferme sui bordi. rest-point.sh boccia solo // quelle: le altre le misura e basta. rest: () => scenes.filter((s) => s.restAtEdges).forEach((s) => out(s.slug)), + // I banchi che dipendono solo dal catalogo: la giunta di ogni coppia + // adiacente. Quelli specifici di una scena sola (focus-sharpness, + // handoff-travel) restano scritti nel workflow: non sono derivabili da un + // elenco, e fingere il contrario nasconderebbe che esistono. + // + // `fill` NON GENERA PIU' UN COMANDO. fill-measure.sh promuove anche una lastra + // arretrata (vedi la sua intestazione), quindi in questo elenco sarebbe un + // verde che non dice niente. Il campo resta nel catalogo: dichiara la promessa + // che il banco geometrico del riempimento verifichera'. measures: () => { for (const s of scenes) { - if (s.fill) out(`./scripts/fill-measure.sh video/out/${s.slug}.mp4`); if (s.seamAfter) { const prev = scenes.find((x) => x.id === s.seamAfter); if (!prev) { diff --git a/scripts/fill-measure.sh b/scripts/fill-measure.sh index 7588379..5c1e4ee 100755 --- a/scripts/fill-measure.sh +++ b/scripts/fill-measure.sh @@ -21,6 +21,26 @@ # istante campionato. Il riferimento Linear ne tiene vivi due su quattro. La # nostra prompt-input tutti e quattro. # +# NON DA' UN VERDETTO, e il motivo e' misurato (15 settembre 2026). Il "20 su +# 20" che questo script stampa sulle sei scene lo stampa anche su una lastra +# arretrata. Ridotto al 60% ui-mockup.mp4 e riempito il resto col colore vero +# del fondale (#1B1C20), la lettura esce 28,79 su tutti e quattro i bordi a ogni +# campione: il fondale delle scene ha luminanza intorno a 20, e la soglia e' 2. +# La soglia era giusta per OrbitLoop, che aveva il fondo nero, e ha smesso di +# esserlo quando il fondo e' diventato quello dell'app. +# +# Neanche la tessitura locale separa i due casi. Il rapporto fra la varianza 3x3 +# della fascia di bordo e quella del centro scende a 0,014 su card-release con +# la lastra in quadro, e sale a 0,043 su board-orbit, dove i bordi mostrano il +# fondale apposta. Dietro la lastra c'e' il piano attenuato (MAT-03), cioe' la +# board disegnata una seconda volta e sfocata, e per i pixel e' contenuto. +# +# Quindi il verdetto sul riempimento non si prende dai pixel: si prende dalla +# geometria, proiettando la lastra ai frame campionati e chiedendo che copra i +# quattro bordi del quadro. Finche' quel banco non esiste lo script resta una +# lettura, esce dalle misure della CI, e CAM-01 sulla pagina e' in grigio. Un +# banco che non puo' fallire non va lasciato in un elenco di banchi che possono. +# # Uso: ./scripts/fill-measure.sh set -uo pipefail @@ -85,3 +105,4 @@ for q in 0.20 0.38 0.56 0.74 0.90; do done echo " bordi vivi: $vivi su $tot campioni" +echo " (lettura, non verdetto: questa soglia promuove anche una lastra arretrata, vedi l'intestazione)" diff --git a/scripts/framelocked-verdict.sh b/scripts/framelocked-verdict.sh index e19859a..5613b26 100755 --- a/scripts/framelocked-verdict.sh +++ b/scripts/framelocked-verdict.sh @@ -15,7 +15,20 @@ # orologio che passa fra l'una e l'altra a far emergere la deriva. Dentro un # singolo render i frame si susseguono troppo in fretta perche' si veda. # -# Uso: ./scripts/framelocked-verdict.sh +# IL VERDETTO NON USCIVA. Fino a settembre 2026 lo script stampava "almeno un +# frame diverge" e usciva 0 lo stesso: in CI una divergenza vera si sarebbe letta +# nel log e sarebbe passata col verde, e nessuno legge un log verde. Adesso esce +# 1, e la CI lo prova sulla sonda FrameLockedProbeRandom, che ha un Math.random +# dentro e deve essere bocciata. +# +# Uso: +# ./scripts/framelocked-verdict.sh i due rami della sonda GSAP +# ./scripts/framelocked-verdict.sh PromptInput una o piu' composition +# FRAMES="150 175 200" ./scripts/framelocked-verdict.sh PromptInput +# +# Esce 0 se ogni frame e' ripetibile e la timeline avanza, 1 se un frame +# diverge o la timeline e' ferma, 3 se un render non e' uscito: in quel caso lo +# strumento non ha misurato niente e un verdetto sulla scena sarebbe inventato. set -uo pipefail cd "$(dirname "$0")/../video" || exit 1 @@ -23,8 +36,13 @@ cd "$(dirname "$0")/../video" || exit 1 TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT -FRAMES=(30 61 92) +# I frame si possono scegliere dall'ambiente perche' il momento che conta non e' +# lo stesso in ogni composition: in PromptInput la digitazione, cioe' il posto +# dove un caso non seminato si vedrebbe, parte al frame 132. +# shellcheck disable=SC2206 +FRAMES=(${FRAMES:-30 61 92}) FAIL=0 +NOMEASURE=0 probe() { local comp="$1" label="$2" @@ -45,7 +63,7 @@ probe() { if [ -z "$ha" ] || [ -z "$hb" ]; then echo " frame $f: RENDER FALLITO" - FAIL=1 + NOMEASURE=1 elif [ "$ha" = "$hb" ]; then echo " frame $f: ripetibile $ha" else @@ -76,14 +94,25 @@ probe() { fi } -echo "FrameLocked: lo stesso frame renderizzato due volte, in due invocazioni." +echo "Lo stesso frame renderizzato due volte, in due invocazioni. Frame: ${FRAMES[*]}" -probe FrameLockedProbe "ticker STACCATO (come dice il §8)" -probe FrameLockedProbeAttached "ticker ATTACCATO (il ramo che il §8 diceva rotto)" +if [ "$#" -eq 0 ]; then + probe FrameLockedProbe "ticker STACCATO (come dice il §8)" + probe FrameLockedProbeAttached "ticker ATTACCATO (il ramo che il §8 diceva rotto)" +else + for comp in "$@"; do + probe "$comp" "$comp" + done +fi echo "" -if [ "$FAIL" -eq 0 ]; then - echo "VERDETTO: nessuna divergenza in nessuno dei due rami." -else - echo "VERDETTO: almeno un frame diverge. Vedi sopra quale ramo." +if [ "$NOMEASURE" -eq 1 ]; then + echo "VERDETTO: nessuno. Almeno un render non e' uscito, quindi non ho misurato." + exit 3 +fi +if [ "$FAIL" -eq 1 ]; then + echo "VERDETTO: almeno un frame diverge, o la timeline e' ferma. Vedi sopra dove." + exit 1 fi +echo "VERDETTO: ogni frame e' ripetibile e la timeline avanza." +exit 0 diff --git a/showcase/grammatica.html b/showcase/grammatica.html index fabf1cc..0a00e22 100644 --- a/showcase/grammatica.html +++ b/showcase/grammatica.html @@ -322,11 +322,12 @@

Thirty-six movements,
no cuts.

ten have no interface on screen at all, because in those the sentence is the film.

-

- Thirty-two of the thirty-six name a bench that exists and runs in CI. - The other four name one that does not exist yet, and those are printed - in grey: a measurement nobody has written is a proposal, and this page - would be doing the thing it warns against if it let the two look alike. +

+ Twenty-nine of the thirty-six name a bench that exists, runs in CI and + can fail. The other seven are printed in grey: six name a bench nobody + has written yet, and one names a bench that exists but cannot run in + CI. A measurement nobody runs is a proposal, and this page would be + doing the thing it warns against if it let the two look alike.

Back to the scenes @@ -417,9 +418,9 @@

Thirty-six movements,
no cuts.

seam.shA join that is really a cut in costume29 px against 59,895, 2065× handoff-travel.shA card that does not actually cross. A freeze frame passes the other checks with full marks, and for months this one did not run at all on macOS, printing a diagnosis about the scene for a fault in itself210 px, monotonic centroid beats.shA promised beat that is not on screen. The answer was assembling under the composer2 → 8 → 10 words streamed - fill-measure.shA frame that lets its edges die20 of 20 samples, four sides alive - legibility.shText that does not survive being scaled downholds under 640 px wide - framelocked-verdict.shA scene that is not reproducible: it still renders, with different pixelssame pixels every render + fill-measure.shA frame that lets its edges die, which it cannot catch. The scenes sit on a background as bright as its threshold, so a slab shrunk to 60 per cent with the real background around it reads the same as a full frame. Kept as a readout and taken out of CI until the check is geometric20 of 20, on the defect too + legibility.shText that does not survive being scaled down. It reads the reference films beside the render, and those are not in git, so it runs on a laptop and not in CIholds under 640 px wide + framelocked-verdict.shA scene that is not reproducible: it still renders, with different pixels. For months it printed its verdict and exited 0 either way, so it now has to fail a probe with a Math.random inside before its green countssame pixels every render, the probe fails focus-sharpness.shA slab that stopped being DOM: the text falls apart once the camera is on top of it2.09× against 1.03× on the fixture rest-point.shA scene still moving where the next one has to attach. Its thresholds were absolute to begin with, tuned on this machine, and every one of them failed the move to Linuxedges under 30% of mid-scene click-gap.shA press and its consequence fused onto one frame, or so far apart that it stops reading as a consequence and starts reading as lagf271 and f276, five frames @@ -1770,11 +1771,11 @@

Thirty-six movements,
no cuts.

]; var CAT=[ -{fam:"cam",code:"CAM-01",t:"Straightening push-in",demo:"push",src:"slab.ts · START/END_POSE", +{fam:"cam",code:"CAM-01",p:1,t:"Straightening push-in",demo:"push",src:"slab.ts · START/END_POSE", a:"The slab enters from off-frame at an angle and arrives at rest, more frontal than it started. The closing yaw is how you say it can be read now.", b:"Two poses in slab.ts and one interpolate between them. The final pose lives in the module rather than in the scene, because it is where the next one attaches.", c:"yaw -18 to -9, pitch 5 to 2.5, pushZ 0 to 48, slideX 900 to 0. 108 frames of inOut cubic, then 42 at rest.", - d:"fill-measure.sh. A push-in that starts too tight lets an edge die: on the repo the reading is 20 samples out of 20 with all four sides alive."}, + d:"A geometric fill check: project the slab at the sampled frames and require it to cover all four edges of the frame. fill-measure.sh was that bench and could not fail: it reads 20 samples out of 20 on a slab shrunk to 60 per cent with the real background around it, because the background is as bright as its threshold and the blurred plane behind the slab looks like content to any pixel measure."}, {fam:"cam",code:"CAM-02",t:"Truck with parallax",demo:"truck",src:"translateZ per layer", a:"The camera slides sideways and sidebar, board and panel separate because they sit at different depths. It is what tells a slab apart from a photograph being panned.", @@ -1782,11 +1783,11 @@

Thirty-six movements,
no cuts.

c:"header 6, sidebar 10, panel 18, board 26, active card 40, and the truck runs pushed in at 780 rather than from the wide pose. That is not a taste: at the wide pose those same offsets buy four pixels of shear over the whole move, which is real and invisible. Close in they buy twenty, and the readout measures them rather than claiming them.", d:"demo-check.py. Panel and board travel different pixels even flat, because they sit at different points on an inclined plane, so the differential travel on its own measures nothing. What measures the depth is how much that difference changes when the z offsets come off: nineteen pixels, and the readout takes them off the rendering rather than from this sentence."}, -{fam:"cam",code:"CAM-03",t:"Orbit that shows the edge",demo:"orbit",src:"scenes/BoardOrbit.tsx", +{fam:"cam",code:"CAM-03",p:2,t:"Orbit that shows the edge",demo:"orbit",src:"scenes/BoardOrbit.tsx", a:"The camera goes around the slab far enough to show it has an edge. It establishes the thing as an object, and a film does that once.", b:"A second plane behind the first, slightly larger, with the contact shadow. Past forty degrees of yaw the text stops reading, so the orbit stops before that.", c:"In the film: from frontal out to -34 over 118 frames, then 32 frames of stillness, so the piece ends on a pose and not on an interrupted move. Thirty-four is where the board still reads. In the demo: yaw +10 to -36, then out to -56 and back. The overshoot is there so the limit is something you see rather than something you are told: past forty degrees the board foreshortens to 59 per cent of its width and the titles stop being titles. Edge 30, shadow 90 offset and 180 blur.", - d:"legibility.sh. A wide orbit drops the text under the legibility threshold, and the bench says down to what size it survives instead of leaving it to taste."}, + d:"legibility.sh. A wide orbit drops the text under the legibility threshold, and the bench says down to what size it survives instead of leaving it to taste. It reads the reference films beside the render, and those are not in git, so it cannot run in CI."}, {fam:"cam",code:"CAM-04",t:"Descent to macro",demo:"macro",src:"scenes/CardFocus.tsx", a:"The camera carries on past the readable pose and into a single element, which fills the frame and stays sharp.", @@ -1980,7 +1981,7 @@

Thirty-six movements,
no cuts.

c:"yaw -30 closing to -19, pitch 3, inside the range the slab's own poses cover — UIMockup starts at -18. At the slab's closing angles the foreshortening is real and invisible: the two ends of the line differ by a couple of per cent, which measures but does not read. The second half lays the plane flat, which is what type does when nobody puts it anywhere, and turns it back before the loop closes; the sentence never leaves.", d:"demo-check.py compares the rendered height of the first word against the last: 0.909 on the plane, because perspective makes the near end larger, and 1.000 flat. It is the cheapest test for whether type is in the room or on the glass."}, -{fam:"giu",code:"GIU-04",t:"Monotonic derivative",demo:"chain",src:"the three boundary poses", +{fam:"giu",code:"GIU-04",p:1,t:"Monotonic derivative",demo:"chain",src:"the three boundary poses", a:"Across the whole chain yaw, pitch and push always move the same way. Not fussiness: the eye follows the derivative of the movement, and a reversal at a join reads as a cut even when the pixels of the two frames match.", b:"The boundary poses live in one module, so the monotonicity is checked by reading three constants instead of opening three scenes. The graph in the corner is exactly that: the three quantities across the chain, with the two joins marked.", c:"yaw -18, -9, -4, 0. pitch 5, 2.5, 1.2, 0. push 0, 48, 96, 1493.6. None of the three ever goes back.", @@ -2011,7 +2012,7 @@

Thirty-six movements,
no cuts.

var spec=el("dl","spec"); [["What it does",x.a,""],["How it is built",x.b,""],["Numbers",x.c,""], - [x.p?"The bench it needs, not written yet":"The bench that fails it",x.d,x.p?"bench prop":"bench"]].forEach(function(r){ + [x.p===2?"The bench it has, not run in CI":x.p?"The bench it needs, not written yet":"The bench that fails it",x.d,x.p?"bench prop":"bench"]].forEach(function(r){ var d=el("div",r[2]||null); d.appendChild(el("dt",null,null,r[0])); d.appendChild(el("dd",null,null,r[1])); @@ -2024,6 +2025,32 @@

Thirty-six movements,
no cuts.

host.appendChild(sec); }); +/* IL CONTEGGIO DEI BANCHI SI CALCOLA DAI DATI. Scritto a mano diceva trentadue + su trentasei mentre ventinove erano davvero banchi che possono fallire in CI: + le voci crescevano e la frase no. Il testo nell'HTML resta come ripiego senza + script, e dice la stessa cosa. */ +(function(){ + var N=["zero","one","two","three","four","five","six","seven","eight","nine","ten", + "eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"]; + var T=["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"]; + function words(n){ return n<20?N[n]:T[Math.floor(n/10)]+(n%10?"-"+N[n%10]:""); } + function name(n){ return words(n)+(n===1?" names":" name"); } + var p=document.getElementById("benchcount"); + if(!p) return; + var real=0, todo=0, noci=0; + CAT.forEach(function(x){ if(!x.p) real++; else if(x.p===2) noci++; else todo++; }); + var s=words(real); + s=s.charAt(0).toUpperCase()+s.slice(1)+" of the "+words(CAT.length)+ + " name a bench that exists, runs in CI and can fail."; + var parts=[]; + if(todo) parts.push(name(todo)+" a bench nobody has written yet"); + if(noci) parts.push(name(noci)+" a bench that exists but cannot run in CI"); + if(parts.length) s+=" The other "+words(todo+noci)+(todo+noci===1?" is":" are")+ + " printed in grey: "+parts.join(", and ")+"."; + p.textContent=s+" A measurement nobody runs is a proposal, and this page would"+ + " be doing the thing it warns against if it let the two look alike."; +})(); + /* filtri e pausa */ var chips=[].slice.call(document.querySelectorAll(".chip[data-fam]")); chips.forEach(function(ch){ diff --git a/video/src/Root.tsx b/video/src/Root.tsx index cdfff97..46a45a6 100644 --- a/video/src/Root.tsx +++ b/video/src/Root.tsx @@ -100,6 +100,20 @@ export const RemotionRoot: React.FC = () => { height={1080} defaultProps={{ detachTicker: false }} /> + {/* LA SONDA CHE IL BANCO DEVE BOCCIARE. Stessa scena delle due sopra, con + un Math.random dentro: framelocked-verdict.sh deve uscire 1 su questa, + e la CI lo verifica. Fino a settembre 2026 il banco stampava il + verdetto e usciva sempre 0, quindi una divergenza vera sarebbe passata + col verde. */} + ); }; diff --git a/video/src/scenes/FrameLockedProbe.tsx b/video/src/scenes/FrameLockedProbe.tsx index 86df054..c99e00b 100644 --- a/video/src/scenes/FrameLockedProbe.tsx +++ b/video/src/scenes/FrameLockedProbe.tsx @@ -14,11 +14,18 @@ import { app, fontStack, monoStack, radius } from "../theme"; * si vede negli hash dei PNG anche quando l'occhio non la coglie. * * Come si legge il verdetto: `scripts/framelocked-verdict.sh`. + * + * `jitter` E' IL CONTROLLO NEGATIVO DEL BANCO, non una variante da usare. Sposta + * il quadrato di una quantita' presa da Math.random, cioe' esattamente il guasto + * che il banco esiste per trovare: il video esce lo stesso, e due render dello + * stesso frame danno pixel diversi. Senza una sonda che il banco deve bocciare, + * il suo verde vorrebbe dire soltanto che e' arrivato in fondo. */ -export const FrameLockedProbe: React.FC<{ detachTicker?: boolean }> = ({ - detachTicker = true, -}) => { +export const FrameLockedProbe: React.FC<{ + detachTicker?: boolean; + jitter?: boolean; +}> = ({ detachTicker = true, jitter = false }) => { const bar = useRef(null); const box = useRef(null); @@ -43,6 +50,14 @@ export const FrameLockedProbe: React.FC<{ detachTicker?: boolean }> = ({ return tl; }, []); + // Il guasto voluto, vedi sopra: fuori dalla sonda negativa vale zero. + // L'INTERVALLO E' LARGO APPOSTA. Chrome allinea la posizione al pixel intero, + // quindi con 40 px due render avevano una probabilita' su 40 di finire nello + // stesso punto, e su un Mac e' successo al primo tentativo. Con 400 i tre + // frame del banco coincidono per caso una volta su 64 milioni. + // eslint-disable-next-line @remotion/deterministic-randomness + const offset = jitter ? Math.random() * 400 : 0; + return ( = ({ ref={box} style={{ position: "absolute", - left: 880, + left: 880 + offset, top: 620, width: 160, height: 160, diff --git a/video/src/scenes/catalog.json b/video/src/scenes/catalog.json index 78c63b0..78a6b02 100644 --- a/video/src/scenes/catalog.json +++ b/video/src/scenes/catalog.json @@ -12,7 +12,9 @@ "", "CAMPI. id = composition Remotion. slug = nome del file .mp4 e sorgente del", "