// Copione del video breve dell'Ingorgo Sonoro (Andrea, 30 ottobre: «45 secondi, che parta come... ci sono cose che non // tornano..... forse..., e fai vedere il manifesto dell'Ingorgo e girare tra la musica»). Come i trailer // (video/trailer.mjs): la scena a tutto schermo, scritte e stacchi al nero nella pagina, niente clic; la musica la // sceglie il copione, una console per posto (out/ingorgo-sonoro-audio.json). // Prova: OUT=/tmp/prova COPIONE=./ingorgo-sonoro.mjs node video/prova-locale.mjs // Ripresa come la partita completa (video/SCALETTA.md), poi: python3 video/monta.py --trailer . import { mkdirSync, writeFileSync } from "node:fs"; const c = (id, it) => ({ id, it, en: it, top: true }); export const title = { it: "La Leggenda del Regolo · l'Ingorgo Sonoro", en: "La Leggenda del Regolo · l'Ingorgo Sonoro" }; export const cues = [ c("dubbio", "Ci sono cose che non tornano…"), c("manifesto", "il manifesto"), c("incanto", "l'incantesimo"), c("festa", "la festa"), c("titolo", "La Leggenda del Regolo"), ]; const URL_GIOCO = "http://127.0.0.1:8000/"; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const H = "assets/audio/ingorgo-house.mp3", L = "assets/audio/ingorgo-latino.mp3", U = "assets/audio/ingorgo-underground.mp3", T = "assets/audio/ingorgo-techno.mp3"; export async function setup(d) { const p = d.page; await p.goto(URL_GIOCO); await p.evaluate(() => localStorage.clear()); await p.reload(); await sleep(500); if (await p.evaluate(() => document.getElementById("splash").classList.contains("wait"))) await p.click("#splash"); await p.waitForSelector("#title:not([hidden])"); await p.click("#new-game"); await p.click('#rione [data-rione="campomigliaio"]'); await p.click("#skip-intro"); await p.addStyleTag({ content: ` #side, #corner, #sound, #closeup-close, #bocce-controls, #briscola-controls, #tressette-controls, #scopa-controls, #map-button, #pause-button, #spots-button, #notebook-button, #sentence { display: none !important; } body { background: #000 !important; overflow: hidden; } #app { position: fixed !important; inset: 0 !important; width: 100vw !important; margin: 0 !important; } #stage, #stage.biliardo, #stage.briscola { width: 100vw !important; height: 100vh !important; } #scene { width: 100vw !important; height: 100vh !important; } #title .buttons, #title .version, #title .dedication { display: none !important; } #title::before { background-image: url(assets/scenes/ingorgo-piazza.png) !important; } #title::after { background: linear-gradient(rgba(20, 12, 30, .6), rgba(20, 12, 30, .1) 45%, rgba(20, 12, 30, .75)), url(assets/scenes/ingorgo-piazza.png) center / contain no-repeat !important; } #tr-nero { position: fixed; inset: 0; background: #000; opacity: 1; transition: opacity .7s; z-index: 50; pointer-events: none; } #tr-scritta { position: fixed; left: 0; right: 0; top: 50%; transform: translateY(-50%); text-align: center; z-index: 51; font-family: "Press Start 2P", monospace; font-size: 40px; line-height: 1.7; color: #ffe9b0; opacity: 0; white-space: pre-line; transition: opacity .8s; text-shadow: 4px 4px 0 #2a1608, 0 0 24px rgba(0,0,0,.9); pointer-events: none; padding: 0 60px; } #tr-scritta.basso { top: auto; bottom: 70px; transform: none; font-size: 30px; } ` }); await p.evaluate(() => { for (const id of ["tr-nero", "tr-scritta"]) { const e = document.createElement("div"); e.id = id; document.body.appendChild(e); } }); await p.evaluate(() => setInterval(() => dispatchEvent(new PointerEvent("pointermove")), 10000)); await p.evaluate(async () => { const { settle } = await import('/tests/helpers.mjs'); settle(game); window.voices?.stop?.(); }); d.vm("DISPLAY=:0 ~/lab/video/mv.py 1599 899 0.2"); await sleep(1200); } export async function run(d) { const p = d.page; // L'orologio dell'audio, come nei trailer: le voci che partono; la musica la decide il copione. await p.evaluate(() => { window.__t0 = Date.now(); window.__audio = []; const t = () => (Date.now() - window.__t0) / 1000; const path = (src) => decodeURI(new URL(src, location.href).pathname.slice(1)); let voice = null; window.__musica = (src) => window.__audio.push({ t: t(), music: src }); const look = () => { const a = window.voices?.audio, now = a && !a.paused && !a.ended ? a : null; if (now !== voice) { if (voice) window.__audio.push({ t: t(), voiceEnd: true }); if (now) window.__audio.push({ t: t(), voice: path(now.src) }); voice = now; } requestAnimationFrame(look); }; look(); }); const musica = (src) => p.evaluate((s) => window.__musica(s), src); const nero = (on, s = 0.7) => p.evaluate(([on, s]) => { const e = document.getElementById("tr-nero"); e.style.transition = `opacity ${s}s`; e.style.opacity = on ? 1 : 0; }, [on, s]); const scritta = (testo, basso = false) => p.evaluate(([t, b]) => { const e = document.getElementById("tr-scritta"); if (t) { e.textContent = t; e.classList.toggle("basso", b); } e.style.opacity = t ? 1 : 0; }, [testo, basso]); const vai = (scene, at, flags = {}) => p.evaluate(async ([scene, at, flags]) => { const { settle } = await import('/tests/helpers.mjs'); game.closeup = null; game.queue.length = 0; game.speech = null; game.dialogue = null; game.path = []; game.walking = false; game.task = null; Object.assign(game.state.flags, flags); game.go(scene, at); settle(game); }, [scene, at, flags]); const cammina = (a) => p.evaluate((a) => game.walkTo(a), a); // 1. Il nero e il dubbio. await musica("assets/audio/sera.mp3"); await d.cue("dubbio"); await sleep(500); await scritta("Ci sono cose\nche non tornano…"); await sleep(2400); await scritta(null); await sleep(600); await scritta("…forse…"); await sleep(1500); await scritta(null); await sleep(400); // 2. La piazza di giorno: Andrea va al manifesto, che si apre ingrandito. await d.cue("manifesto"); await musica("assets/audio/paese.mp3"); await vai("piazza", [380, 320]); await nero(false); await sleep(700); await p.evaluate(() => game.act("look", "manifesto_ingorgo")); await p.waitForFunction(() => game.closeup?.id === "manifesto_ingorgo" && game.closeup.t > 0.5, null, { timeout: 15000 }); await sleep(2000); // Il tocco sul manifesto: la battuta sul manifesto si salta, resta il desiderio, a voce. await p.evaluate(() => game.closeupTap()); for (let k = 0; k < 200 && !(await p.evaluate(() => game.speech?.text.startsWith("Quanto darei"))); k++) { await p.evaluate(() => { if (game.speech) game.skip(); }); await sleep(40); } // 3. L'incantesimo: nel lampo parte la house della piazza. await d.cue("incanto"); await p.waitForFunction(() => game.closeup?.id === "ingorgo", null, { timeout: 20000 }); await p.waitForFunction(() => game.closeup?.swapped, null, { timeout: 10000 }); await musica(H); // 4. Il giro della festa: Andrea si accorge della festa camminando fra la gente (la seconda battuta si salta), poi una // console per posto, a stacchi veloci. await p.waitForFunction(() => !game.closeup && game.speech?.text.toLowerCase().includes("ingorgo"), null, { timeout: 10000 }); await d.cue("festa"); await p.evaluate(() => { game.queue.length = 0; game.walkTo([200, 300]); }); await p.waitForFunction(() => !game.speech?.text.toLowerCase().includes("ingorgo"), null, { timeout: 20000 }); await p.evaluate(() => { game.queue.length = 0; game.speech = null; }); await sleep(300); for (const [scene, da, a, brano] of [["via_antica_posta", [330, 345], [300, 290], H], ["piazzetta_comune", [260, 350], [300, 300], L], ["antica_posta_alta", [240, 350], [380, 290], U], ["piazzetta", [330, 352], [420, 290], T]]) { await nero(true, 0.2); await sleep(220); await musica(brano); await vai(scene, da); await nero(false, 0.2); await cammina(a); await sleep(3200); } await sleep(300); // 5. Il titolo sopra la piazza della festa. await d.cue("titolo"); await nero(true, 0.9); await sleep(1000); await p.evaluate(() => { game.closeup = null; document.getElementById("title").hidden = false; }); await nero(false, 1.2); await sleep(1200); await scritta("l'Ingorgo Sonoro", true); await sleep(3000); await scritta(null); await nero(true, 1.4); await sleep(1600); const audio = await p.evaluate(() => window.__audio); const out = new URL("./out/", import.meta.url); mkdirSync(out, { recursive: true }); writeFileSync(new URL("ingorgo-sonoro-audio.json", out), JSON.stringify({ eventi: audio }, null, 2)); }