Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion .github/workflows/showcase.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
42 changes: 33 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 9 additions & 6 deletions scripts/catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
21 changes: 21 additions & 0 deletions scripts/fill-measure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 <video.mp4>
set -uo pipefail

Expand Down Expand Up @@ -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)"
49 changes: 39 additions & 10 deletions scripts/framelocked-verdict.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,34 @@
# 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

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"
Expand All @@ -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
Expand Down Expand Up @@ -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
Loading
Loading