Bocce: il clic fissa la mira come al biliardo, così andando verso la forza o «Lancia» il puntatore non la sposta più (scoperto girando il video: la boccia partiva a due passi). Copioni dei video «Come si gioca» di scopa, tressette, bocce e biliardo, e video/sovrapposte.py per controllare che il narratore non parli sopra i personaggi

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
2026-10-26 21:23:23 +01:00
co-authored by Claude Opus 5.5
parent 111b892916
commit 3019a6e32f
7 changed files with 376 additions and 4 deletions
+6 -4
View File
@@ -457,6 +457,7 @@ canvas.addEventListener('pointerdown', e => {
if (!aimed && !game.speech) return;
if (aimed) game.closeup.aimLocked = true; // il clic fissa la mira
}
if (story.closeups[game.closeup.id].type === 'bocce' && aimed) game.closeup.aimLocked = true;
press = { closeup: true };
// Alle bocce si tiene premuto per caricare la forza (la battuta in corso, un tocco la salta).
if (!game.skip()) game.closeupPress();
@@ -503,10 +504,11 @@ canvas.addEventListener('contextmenu', e => {
canvas.addEventListener('pointermove', e => {
if (!running) return;
mouse = e.pointerType === 'mouse' ? scenePoint(e) : null;
// Al biliardo la stecca segue il mouse finché un clic (o una regolazione fine) non fissa la mira: dopo,
// attraversare il tavolo per raggiungere «Tira» non cambia il colpo. Al tiro successivo torna libera.
const billiards = game.closeup && story.closeups[game.closeup.id].type === 'biliardo';
if (billiards) { if (e.pointerType === 'mouse' && !press && !game.closeup.aimLocked) game.closeupAim(scenePoint(e)); }
// Al biliardo e alle bocce la mira segue il mouse finché un clic (o una regolazione fine) non la fissa: dopo,
// attraversare il campo per raggiungere la forza, «Tira» o «Lancia» non cambia il colpo. Al tiro dopo torna libera.
// (Bocce: scoperto girando il video «Come si gioca», la mira finiva a due passi andando verso il cursore.)
const aiming = game.closeup && ['biliardo', 'bocce'].includes(story.closeups[game.closeup.id].type);
if (aiming) { if (e.pointerType === 'mouse' && !press && !game.closeup.aimLocked) game.closeupAim(scenePoint(e)); }
else if (e.pointerType === 'mouse' || press?.closeup) game.closeupAim(scenePoint(e));
});
canvas.addEventListener('pointerleave', () => { mouse = null; });
+6
View File
@@ -387,6 +387,12 @@ try {
await bocce.mouse.up();
assert.equal(await bocce.evaluate(() => game.closeup.balls.length), 1, 'lasciando, la boccia parte');
await bocce.waitForFunction(() => game.closeup.balls.length === 2, null, { timeout: 15000 });
// Il clic fissa la mira: andando verso la forza o «Lancia» il puntatore attraversa il campo e non la sposta.
await bocce.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'player', null, { timeout: 30000 });
await bocce.mouse.click(r.x + r.width * 0.45, r.y + r.height * 0.6);
const fixed = await bocce.evaluate(() => JSON.stringify(game.closeup.aim));
await bocce.mouse.move(r.x + r.width * 0.5, r.y + r.height * 0.95, { steps: 8 });
assert.equal(await bocce.evaluate(() => JSON.stringify(game.closeup.aim)), fixed, 'la mira resta dove si è cliccato');
await bocce.keyboard.press('Escape');
await bocce.waitForFunction(() => game.speech?.text.startsWith('Già stanco?'), null, { timeout: 5000 });
await bocce.close();
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
"""Controlla che nel video «Come si gioca» il narratore non parli sopra i personaggi del gioco.
python3 video/sovrapposte.py tutorial-briscola (dopo la ripresa: cues.json, registro audio, narrazione)
"""
import json
import sys
from pathlib import Path
VIDEO = Path(__file__).resolve().parent
clip = sys.argv[1]
cues = json.loads((Path.home() / "Workspaces/qemu/qemu-iso-lab/artifacts/tour/out" / clip / "cues.json").read_text())["cues"]
narr = {v["id"]: v["sec"] for v in json.loads((VIDEO / "out" / f"{clip}-narrazione.json").read_text())["voci"]}
voci, aperta = [], None
for e in json.loads((VIDEO / "out" / f"{clip}-audio.json").read_text())["eventi"]:
if "voice" in e:
aperta = [e["t"], None, e["voice"]]
voci.append(aperta)
elif "voiceEnd" in e and aperta:
aperta[1], aperta = e["t"], None
male = [(c["id"], v[2]) for c in cues for v in voci if v[1] and v[0] < c["start"] + narr[c["id"]] and v[1] > c["start"]]
for id_, voce in male:
print(f"✗ «{id_}» sopra {voce}")
print(f"{clip}: {len(voci)} voci del gioco, {len(cues)} frasi del narratore, {len(male)} sovrapposte")
sys.exit(1 if male else 0)
+92
View File
@@ -0,0 +1,92 @@
// Copione del video «Come si gioca: il biliardo» (video/SCALETTA-CARTE.md, clip 5).
// Giro: video/narra.py, poi rec.mjs come in video/SCALETTA.md, poi video/monta.py <cartella> --tutorial.
import { c, sleep, setup as base, regista } from "./tutorial-comune.mjs";
export const title = { it: "Come si gioca · il biliardo", en: "Come si gioca · il biliardo" };
export const cues = [
c("dove", "Il biliardo è al Circolo 4 Maggio, in piazza. L'Ingegner Tozzi gioca ai cinque birilli, e si va a venti"),
c("livello", "Anche qui si sceglie quanto deve giocare forte"),
c("tavolo", "Il tavolo dall'alto: la tua biglia, quella dell'avversario, il pallino, e in mezzo il castello dei birilli"),
c("scopo", "Si colpisce la propria biglia e la si manda su quella dell'avversario: i birilli che lei abbatte fanno punti a chi tira"),
c("mira", "La stecca segue il mouse, e un clic fissa la mira. La linea tratteggiata è la strada della biglia; le frecce, dove vanno le due biglie dopo l'urto"),
c("tira", "Con le frecce si gira la stecca, con Maiuscolo ancora più piano. Poi si regola la forza, e si preme Tira"),
c("valori", "Il birillo bianco vale due, il rosso quattro, il rosso da solo dieci. Il pallino toccato dalla biglia avversaria, tre"),
c("fallo", "Attenzione ai falli: mancare la biglia avversaria, toccare prima il pallino, o buttare giù birilli con la propria. I punti vanno all'avversario"),
c("fine", "Un colpo a testa, finché qualcuno arriva a venti. Buona partita!"),
];
export const setup = base;
export async function run(d) {
const p = d.page, r = regista(d, "tutorial-biliardo");
await r.ascolta();
await r.vaiA("circolo_4_maggio", [470, 300]);
await r.cue("dove");
await r.parlaCon("ing_tozzi");
await r.scegli("una partita ai birilli");
await r.cue("livello");
await r.scegli("Alla bona.");
// Una partita fissa: un colpo da otto, un fallo voluto per farlo vedere, poi quattro e tre; vince Andrea 21 a 2.
await p.evaluate(async () => {
const { newBiliardo } = await import("/src/engine/biliardo.js");
game.closeup = { id: "biliardo", ...newBiliardo(2, { level: "bona" }) };
});
await sleep(800);
await r.cue("tavolo");
await r.muovi([200, 200], 0.9); await sleep(300); await r.muovi([400, 180], 0.9);
await r.cue("scopo");
// Il colpo: il migliore fra quelli provati a vuoto (o, la seconda volta, un fallo apposta). Si mira col clic sul
// tavolo lungo la direzione scelta, poi forza col cursore e Tira.
const colpo = (sbaglia) => p.evaluate(async (sbaglia) => {
const L = await import("/src/engine/biliardo.js"), c = game.closeup, b = c.balls.player, o = c.balls.cpu;
let top = null;
if (sbaglia) for (let a = 0; a < Math.PI * 2 && !top; a += 0.05) { if (L.simulate(c, "player", a, 0.12).foul === "miss") top = { angle: a, power: 0.12 }; }
else {
const base = Math.atan2(o.y - b.y, o.x - b.x);
for (let d = -0.35; d <= 0.35; d += 0.01) for (const power of [0.3, 0.4, 0.5, 0.6, 0.7, 0.8]) {
const j = L.simulate(c, "player", base + d, power), gain = j.to === "player" ? j.points : -j.points;
if (!top || gain > top.gain) top = { angle: base + d, power, gain };
}
}
const x = b.x + Math.cos(top.angle) * 45, y = b.y + Math.sin(top.angle) * 45;
return { ...top, at: [L.VIEW.x + x * L.VIEW.k, L.VIEW.y + y * L.VIEW.k] };
}, sbaglia);
const tocca = async () => {
for (let i = 0; i < 900; i++) {
const s = await p.evaluate(async () => {
const { canThrow } = await import("/src/engine/biliardo.js");
return game.closeup.step === "result" ? "fine" : canThrow(game.closeup) && !game.speech ? "tocca" : "aspetta";
});
if (s !== "aspetta") return s === "tocca";
await sleep(150);
}
return false;
};
let n = 0;
while (await tocca()) {
n++;
if (n === 1) await r.cue("mira");
if (n === 2) await r.cue("fallo");
const m = await colpo(n === 2);
await r.muovi(m.at, 0.9); await sleep(400);
await r.clicca(m.at, 0.3);
await p.evaluate(({ angle }) => { game.closeup.angle = angle; game.closeup.aimLocked = true; }, m);
if (n === 1) await r.cue("tira");
await d.hover(p.locator("#bocce-power"), 0.6);
await p.locator("#bocce-power").fill(String(Math.round(m.power * 100)));
await p.evaluate((v) => { game.closeup.power = v; }, m.power);
await sleep(500);
await d.click(p.locator("#bocce-throw"), 0.6);
await p.locator("#scene").scrollIntoViewIfNeeded();
if (n === 1) await r.cue("valori");
}
await p.waitForFunction(() => game.closeup.step === "result", null, { timeout: 120000 });
await sleep(1500);
await r.cue("fine");
await r.aspetta();
await d.click(p.locator("#closeup-close"), 0.6);
await r.fermi(800);
await r.fine();
await r.salvaAudio();
}
+90
View File
@@ -0,0 +1,90 @@
// Copione del video «Come si gioca: le bocce» (video/SCALETTA-CARTE.md, clip 4).
// Giro: video/narra.py, poi rec.mjs come in video/SCALETTA.md, poi video/monta.py <cartella> --tutorial.
import { c, sleep, setup as base, regista } from "./tutorial-comune.mjs";
export const title = { it: "Come si gioca · le bocce", en: "Come si gioca · le bocce" };
export const cues = [
c("dove", "Le bocce si giocano al pallaio, accanto al tennis della chiesa. Mariano, il campione, sfida chiunque"),
c("livello", "Si sceglie quanto deve impegnarsi: alla bona, seria, o a fassi male"),
c("campo", "Il campo visto da dietro: in fondo c'è il pallino, e a sinistra il dettaglio, per vedere da vicino chi è più accosto"),
c("mira", "Per mirare si tocca il campo, o il dettaglio vicino al pallino. Il cerchio azzurro dice dove si fermerebbe la boccia"),
c("forza", "Poi si regola la forza col cursore, e si preme Lancia"),
c("turno", "Tira sempre chi non ha il punto. Chi è dietro può accostare, oppure bocciare: colpire la boccia dell'altro per toglierla di lì"),
c("tieni", "C'è anche un altro modo: si tiene premuto sul campo, la forza sale, e lasciando parte la boccia. Vanno bene anche le frecce e la barra spaziatrice"),
c("punti", "Tre bocce a testa. Alla fine fa un punto ogni boccia più vicina al pallino della migliore dell'avversario"),
c("fine", "E se batti Mariano, ti porta a Brento Sanico. Buona partita!"),
];
export const setup = base;
export async function run(d) {
const p = d.page, r = regista(d, "tutorial-bocce");
await r.ascolta();
await r.vaiA("tennis_chiesa", [430, 310]);
await r.cue("dove");
await r.parlaCon("mariano");
await r.scegli("Facciamo una partita");
await r.cue("livello");
await r.scegli("Alla bona.");
// Una mano fissa: accostando bene, Andrea fa tre punti (anche se il tiro a pressione viene un po' corto o lungo).
await p.evaluate(async () => {
const { newBocce } = await import("/src/engine/bocce.js");
game.closeup = { id: "bocce", ...newBocce(9, { level: "bona" }) };
});
await sleep(800);
await p.waitForFunction(() => window.music?.want === "assets/audio/bocce.mp3", null, { timeout: 10000 });
await r.cue("campo");
await r.muovi([300, 200], 1.0); await sleep(400); await r.muovi([90, 170], 1.0);
// Dove mirare: poco prima del pallino, così la boccia si ferma accosto (il cerchio azzurro ci finisce sopra).
const mira = () => p.evaluate(async () => {
const B = await import("/src/engine/bocce.js"), c = game.closeup, u = c.jack.u - 0.55, w = c.jack.w;
const s = B.project(u, w);
return { at: [s.x, s.y], u, w, power: B.powerFor(Math.hypot(u, w) - 0.29) };
});
const tocca = async () => {
for (let i = 0; i < 600; i++) {
const s = await p.evaluate(async () => {
const { canThrow } = await import("/src/engine/bocce.js");
return game.closeup.step === "result" ? "fine" : canThrow(game.closeup) && !game.speech ? "tocca" : "aspetta";
});
if (s !== "aspetta") return s === "tocca";
await sleep(150);
}
return false;
};
let tiro = 0;
while (await tocca()) {
tiro++;
const m = await mira();
if (tiro === 1) await r.cue("mira");
if (tiro < 3) {
// Mira col clic, forza col cursore, poi Lancia.
await r.clicca(m.at, 0.8);
await p.evaluate(async ({ u, w }) => (await import("/src/engine/bocce.js")).aim(game.closeup, u, w), m);
if (tiro === 1) await r.cue("forza");
await d.hover(p.locator("#bocce-power"), 0.6);
await p.locator("#bocce-power").fill(String(Math.round(m.power * 100)));
await p.evaluate(async (v) => (await import("/src/engine/bocce.js")).setPower(game.closeup, v), m.power);
await sleep(600);
await d.click(p.locator("#bocce-throw"), 0.6);
if (tiro === 1) await r.cue("turno");
} else {
// L'ultima tenendo premuto: la forza sale finché non si lascia.
await r.cue("tieni");
await r.muovi(m.at, 0.8);
const [a, b] = await r.punto(...m.at);
d.vm(`DISPLAY=:0 xdotool mousemove ${Math.round(a)} ${Math.round(b)} mousedown 1 sleep ${(m.power * 1.6).toFixed(2)} mouseup 1`);
}
await p.locator("#scene").scrollIntoViewIfNeeded();
}
await p.waitForFunction(() => game.closeup.step === "result", null, { timeout: 90000 });
await sleep(1000);
await r.cue("punti");
await r.cue("fine");
await r.aspetta();
await d.click(p.locator("#closeup-close"), 0.6);
await r.fermi(800);
await r.fine();
await r.salvaAudio();
}
+79
View File
@@ -0,0 +1,79 @@
// Copione del video «Come si gioca: la scopa» (video/SCALETTA-CARTE.md, clip 3).
// Giro: video/narra.py, poi rec.mjs come in video/SCALETTA.md, poi video/monta.py <cartella> --tutorial.
import { c, sleep, setup as base, regista } from "./tutorial-comune.mjs";
export const title = { it: "Come si gioca · la scopa", en: "Come si gioca · la scopa" };
export const cues = [
c("dove", "Allo stesso tavolo dell'oratorio si gioca anche a scopa, in due: basta chiedere «Una scopa?»"),
c("mazzo", "Mazzo e livello si scelgono come per la briscola"),
c("valori", "Quattro carte in tavola e tre in mano. L'Asso vale uno, le altre il loro numero, il gobbo otto, la donna nove e il regio dieci"),
c("presa", "Con una carta prendi quella dello stesso valore; se non c'è, un gruppo di carte che fa la stessa somma. Se puoi prendere, devi"),
c("scelta", "Quando le prese possibili sono più d'una, scegli tu: tocca una carta in tavola, oppure usa i pulsanti qui sotto"),
c("scopa", "Se con la tua carta pulisci tutta la tavola, è scopa: un punto in più"),
c("resto", "Finite le tre carte se ne danno altre tre, finché c'è mazzo. Quel che resta in tavola alla fine va all'ultimo che ha preso"),
c("conto", "A fine smazzata un punto a chi ha più carte, uno a chi ha più mattoni, uno per il settebello, il sette di mattoni, e uno per la primiera"),
c("primiera", "La primiera somma la carta migliore di ogni seme: il sette vale ventuno, il sei diciotto, l'Asso sedici"),
c("fine", "Si va agli undici punti, una smazzata dopo l'altra. Buona scopa!"),
];
export const setup = base;
export async function run(d) {
const p = d.page, r = regista(d, "tutorial-scopa");
await r.ascolta();
await r.vaiA("oratorio", [470, 300]);
await r.cue("dove");
await r.parlaCon("giocatore_carte");
await r.scegli("Una scopa?");
await r.cue("mazzo");
await r.scegli("Con le toscane.");
await r.scegli("Seria.");
// Una smazzata fissa: Andrea sceglie fra due prese, fa due scope e la vince 5 a 1.
await p.evaluate(async () => {
const { newScopa } = await import("/src/engine/scopa.js");
game.closeup = { id: "scopa", ...newScopa(73, { level: "seria", deck: "toscane" }) };
});
await sleep(800);
await p.waitForFunction(() => window.music?.want === "assets/audio/carte.mp3", null, { timeout: 10000 });
await r.cue("valori");
await r.muovi([320, 160], 1.0); await sleep(500); await r.muovi([320, 300], 1.0);
await r.cue("presa");
// Una mossa di Andrea, come la sceglierebbe un giocatore attento; la scelta fra più prese si fa col pulsante.
let k = 1, scelta = false, scopa = false;
for (let i = 0; i < 400; i++) {
const s = await p.evaluate(async () => {
const { canPlay } = await import("/src/engine/scopa.js");
return ["smazzata", "result"].includes(game.closeup.step) ? "fine" : canPlay(game.closeup) && !game.speech ? "tocca" : "aspetta";
});
if (s === "fine") break;
if (s === "aspetta") { await sleep(150); continue; }
const m = await p.evaluate(async (k) => {
const S = await import("/src/engine/scopa.js"), c = game.closeup, m = S.choose(S.observation(c, "player"), "male", k);
const card = c.hands.player[m.index], many = S.captures(c.table, card).length > 1;
const clean = !!m.captured && m.captured.length === c.table.length;
const { handX, HAND } = await import("/src/engine/scopa-render.js");
return { ...m, many, clean, at: [handX(c.hands.player.length, m.index) + 32, HAND.y + 46] };
}, k++);
if (m.many && !scelta) { scelta = true; await r.cue("scelta"); }
if (m.clean && !scopa) { scopa = true; await r.cue("scopa"); }
await r.clicca(m.at, 0.7);
if (m.many) {
await p.waitForFunction(() => game.closeup.step === "choose", null, { timeout: 5000 });
await sleep(900);
await d.click(p.locator(`#scopa-options [data-option="${m.option}"]`), 0.7);
await p.locator("#scene").scrollIntoViewIfNeeded();
}
if (k === 9) await r.cue("resto");
}
await p.waitForFunction(() => game.closeup.step === "smazzata", null, { timeout: 60000 });
await sleep(1200);
await r.cue("conto");
await r.cue("primiera");
await r.cue("fine");
await r.aspetta();
await d.click(p.locator("#closeup-close"), 0.6);
await r.fermi(800);
await r.fine();
await r.salvaAudio();
}
+79
View File
@@ -0,0 +1,79 @@
// Copione del video «Come si gioca: il tressette ai 21» (video/SCALETTA-CARTE.md, clip 2).
// Giro: video/narra.py, poi rec.mjs come in video/SCALETTA.md, poi video/monta.py <cartella> --tutorial.
import { c, sleep, setup as base, regista } from "./tutorial-comune.mjs";
export const title = { it: "Come si gioca · il tressette ai 21", en: "Come si gioca · il tressette ai 21" };
export const cues = [
c("dove", "Per il tressette ai ventuno servono quattro giocatori: il giocatore di carte chiama due amici, e uno fa da socio ad Andrea"),
c("mazzo", "Mazzo e livello si scelgono come per la briscola"),
c("tavolo", "Il socio è di fronte, gli avversari ai lati. Dieci carte a testa, e si gira verso destra"),
c("accuse", "In alto le accuse: la napoletana, cioè Asso, due e tre dello stesso seme, oppure tre Assi, tre due o tre tre. Valgono punti subito"),
c("seme", "Chi esce gioca quel che vuole; gli altri devono rispondere al seme, se ce l'hanno. Le carte che non si possono giocare diventano scure"),
c("ordine", "Prende la più alta del seme di uscita: il tre, poi il due, l'Asso, il regio, la donna, il gobbo, e giù fino al quattro"),
c("segni", "Chi esce può fare un segno al socio. Si sceglie coi pulsanti qui sotto, o coi tasti B, S e V, e poi si gioca la carta"),
c("striscio", "Striscio vuol dire: di questo seme ne ho ancora, non è l'ultima"),
c("busso", "Busso: prendi con la migliore che hai, e poi ritornami il seme"),
c("volo", "Volo: era l'ultima carta di questo seme"),
c("punti", "L'Asso vale un punto; il tre, il due e le figure un terzo di punto. L'ultima presa vale un punto in più"),
c("smazzata", "A fine smazzata si contano i punti interi, e le accuse si sommano"),
c("fine", "Si gioca smazzata dopo smazzata: la coppia che arriva per prima a ventuno è fuori, e ha vinto. Buon tressette!"),
];
export const setup = base;
export async function run(d) {
const p = d.page, r = regista(d, "tutorial-tressette");
await r.ascolta();
await r.vaiA("oratorio", [470, 300]);
await r.cue("dove");
await r.parlaCon("giocatore_carte");
await r.scegli("Un tressette ai 21?");
await r.cue("mazzo");
await r.scegli("Con le toscane.");
await r.scegli("Seria.");
// Una smazzata fissa: accuse, e Andrea che usa striscio, busso e volo.
await p.evaluate(async () => {
const { newTressette } = await import("/src/engine/tressette.js");
game.closeup = { id: "tressette", ...newTressette(11, { level: "seria", deck: "toscane" }) };
});
await sleep(800);
await p.waitForFunction(() => window.music?.want === "assets/audio/carte.mp3", null, { timeout: 10000 });
await r.cue("tavolo");
await r.muovi([320, 60], 0.9); await sleep(300); await r.muovi([600, 150], 0.9); await sleep(300); await r.muovi([40, 150], 0.9);
let k = 1, detto = new Set(), seme = false, accuse = false;
for (let i = 0; i < 600; i++) {
const s = await p.evaluate(async () => {
const { canPlay } = await import("/src/engine/tressette.js");
return ["smazzata", "result"].includes(game.closeup.step) ? "fine" : canPlay(game.closeup) && !game.speech ? "tocca" : "aspetta";
});
if (s === "fine") break;
if (s === "aspetta") { await sleep(150); continue; }
// Le accuse si dicono giocando la prima carta: se ne parla quando sono scritte in alto.
if (!accuse && await p.evaluate(() => game.closeup.accuse.length > 0)) { accuse = true; await r.cue("accuse"); }
const m = await p.evaluate(async (k) => {
const T = await import("/src/engine/tressette.js"), c = game.closeup, m = T.choose(T.observation(c, 0), "male", k);
const { handX, CARD } = await import("/src/engine/tressette-render.js");
return { ...m, follow: c.table.length > 0, at: [handX(c.hands[0].length, m.index) + 21, CARD.y + 30] };
}, k++);
if (m.follow && !seme) { seme = true; await r.cue("seme"); await r.cue("ordine"); }
if (m.signal && !detto.has(m.signal)) {
if (!detto.size) await r.cue("segni");
detto.add(m.signal);
await r.cue(m.signal);
}
if (m.signal) await d.click(p.locator(`#tressette-${m.signal}`), 0.6);
await p.locator("#scene").scrollIntoViewIfNeeded();
await r.clicca(m.at, 0.7);
if (k === 7) await r.cue("punti");
}
await p.waitForFunction(() => ["smazzata", "result"].includes(game.closeup.step), null, { timeout: 60000 });
await sleep(1200);
await r.cue("smazzata");
await r.cue("fine");
await r.aspetta();
await d.click(p.locator("#closeup-close"), 0.6);
await r.fermi(800);
await r.fine();
await r.salvaAudio();
}