Compare commits

..
16 Commits
Author SHA1 Message Date
manzolo bb8e6a8885 Versione 0.1.52 2026-10-29 19:11:43 +01:00
manzoloandClaude Opus 5.5 81a1dd1bc3 Video: la partita completa rigirata sul gioco di oggi, il trailer e la versione extended (copioni, prova locale, montaggio --trailer) (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 18:47:44 +01:00
manzoloandClaude Opus 5.5 0f1740a7ad Effetti nei posti nuovi: nuvole, uccelli, la Carza che scorre e luccica alla cascatella e sotto il ponte, fronde, ombre, farfalle e libellule, lampioni la sera; i panorami del terrazzo e di Via Cafaggio con le ombre delle nuvole sulle colline (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 17:34:37 +01:00
manzolo 3d61fc3954 Versione 0.1.51 2026-10-29 17:30:42 +01:00
manzoloandClaude Opus 5.5 6226de3795 La voce di «Di chi è quell'Audi lì?» (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 17:23:59 +01:00
manzoloandClaude Opus 5.5 17824963aa Sotto il ponte delle poste: il viottolo passa sotto l'arco verso gli orti dello zio Mario, alla stazione si sale dalla parte destra del ponte (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 17:19:16 +01:00
manzoloandClaude Opus 5.5 75cbe8250f Via della Compagnia: l'arco porta al forno, alla Pieve si scende fra le due case gialle (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 17:16:40 +01:00
manzoloandClaude Opus 5.5 6b17288962 Via Liguri Magelli: «Di chi è quell'Audi lì?» (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 17:14:59 +01:00
manzolo 262c774718 Versione 0.1.50 2026-10-29 16:54:13 +01:00
manzoloandClaude Opus 5.5 4a2c88f0e3 Passeggiata sulla Carza: avanti si va a sinistra, dietro il grande albero (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 16:47:33 +01:00
manzolo c395b4e890 Versione 0.1.49 2026-10-29 16:46:30 +01:00
manzoloandClaude Opus 5.5 818af5b36f Villa Adami: a destra si scende in Via della Compagnia, in basso a destra alla scuola e in Via Cafaggio; in Via Cafaggio alla scuola si va dalla stradina dopo il cancello della palestra (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 16:39:42 +01:00
manzoloandClaude Opus 5.5 a080c0ea56 Via Liguri Magelli: l'uscita verso il campo del Giannini copre di nuovo la strada dietro la Ghiacciomobile (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 16:32:33 +01:00
manzolo fbeef76f01 Versione 0.1.48 2026-10-29 16:25:09 +01:00
manzoloandClaude Opus 5.5 45d310b9b4 Gli orti dello zio Mario lungo la Carza e la strada fra il bivio della Provinciale e la stazione (Codex dalle foto di Andrea) (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 16:17:35 +01:00
manzoloandClaude Opus 5.5 481e4a9514 Nuovi posti dalle foto di Andrea (Codex): Via della Compagnia, Via Cafaggio in tre tratti fino alla rotonda, la Provinciale sotto i platani, la passeggiata sulla Carza con la cascatella, gli orti, dietro le poste e sotto il ponte delle poste fino alla stazione (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
2026-10-29 16:03:40 +01:00
76 changed files with 1236 additions and 21 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+151 -3
View File
@@ -1603,9 +1603,9 @@
"file": "andrea-a93c65234e.mp3", "file": "andrea-a93c65234e.mp3",
"sec": 4.28 "sec": 4.28
}, },
"andrea|Di chi è quell'Audi nera?": { "andrea|Di chi è quell'Audi lì?": {
"file": "andrea-b9a784137e.mp3", "file": "andrea-cc8a61a779.mp3",
"sec": 1.63 "sec": 1.4
}, },
"arcu|Let it be.": { "arcu|Let it be.": {
"file": "arcu-6ba2f5285a.mp3", "file": "arcu-6ba2f5285a.mp3",
@@ -1731,6 +1731,154 @@
"file": "andrea-2e64b2d5c2.mp3", "file": "andrea-2e64b2d5c2.mp3",
"sec": 4.8 "sec": 4.8
}, },
"andrea|Un piccolo stemma di pietra sul muro. Chissà di chi era, questa casa, un tempo.": {
"file": "andrea-9317777d3a.mp3",
"sec": 4.94
},
"andrea|Il lampione di ferro, attaccato al muro. La sera fa una luce gialla che pare d'altri tempi.": {
"file": "andrea-e400e4ea1d.mp3",
"sec": 6.11
},
"andrea|La palestra della scuola, col tetto a denti di sega.": {
"file": "andrea-5ed0615726.mp3",
"sec": 3.13
},
"andrea|Lassù Monte Cacioli, con le antenne in cima.": {
"file": "andrea-c633ffc747.mp3",
"sec": 3.12
},
"andrea|Il cancello della palestra. Per la scuola si passa dalla stradina più in là.": {
"file": "andrea-f91d1262a4.mp3",
"sec": 5.19
},
"andrea|Chiuso. Per la scuola si passa dalla stradina più in là.": {
"file": "andrea-237197db3a.mp3",
"sec": 4.32
},
"andrea|Una siepe alta come una casa, e l'edera che si mangia il muro.": {
"file": "andrea-2544c4c379.mp3",
"sec": 3.89
},
"andrea|Le villette col giardino e il muretto: qui è tutto in ordine.": {
"file": "andrea-05ade48460.mp3",
"sec": 4.36
},
"andrea|Una villa con gli archi dietro la siepe. Qui le siepi le tosano col righello.": {
"file": "andrea-2e0acd9298.mp3",
"sec": 5
},
"andrea|Un cipresso dritto come un fuso, in mezzo all'aiuola.": {
"file": "andrea-33576f81c3.mp3",
"sec": 3.8
},
"andrea|Una panchina all'ombra.": {
"file": "andrea-ee2789a3a8.mp3",
"sec": 1.38
},
"andrea|Sedersi ora? Prima il palio.": {
"file": "andrea-aa636ddf94.mp3",
"sec": 2.74
},
"andrea|Il palazzo di mattoni coi negozi sotto, di là dalla Provinciale.": {
"file": "andrea-211109ca9d.mp3",
"sec": 4.66
},
"andrea|I platani della Provinciale: d'estate fanno un'ombra che pare una galleria.": {
"file": "andrea-2ce9db22c9.mp3",
"sec": 5.8
},
"andrea|Le villette sotto il bosco, coi giardini curati.": {
"file": "andrea-402f9ec788.mp3",
"sec": 3.1
},
"andrea|La cascatella della Carza: quella che si vede anche dalla Luna.": {
"file": "andrea-e9bb43952d.mp3",
"sec": 3.42
},
"andrea|D'estate si veniva a rinfrescare i piedi qui sotto.": {
"file": "andrea-2d8e58541c.mp3",
"sec": 2.96
},
"andrea|Il greto pieno di sassi. Dighe, ponti, pesci immaginari: qui ci si passava l'estate.": {
"file": "andrea-816bf4c405.mp3",
"sec": 8.38
},
"andrea|Un lampione in mezzo al prato. Di sera la passeggiata la fanno le zanzare.": {
"file": "andrea-74fe2ee88c.mp3",
"sec": 5.16
},
"andrea|Gli orti con la rete: cavoli, insalata e i fagioli sulle canne.": {
"file": "andrea-343e479437.mp3",
"sec": 5.4
},
"andrea|Un cavolo dall'orto di qualcun altro? Poi chi lo sente.": {
"file": "andrea-e842495cf5.mp3",
"sec": 4.17
},
"andrea|Un ponticino di ferro sul fosso. Due passi e sei di là.": {
"file": "andrea-145bc6ff82.mp3",
"sec": 4.21
},
"andrea|Il greto bianco della Carza, con un filo d'acqua in mezzo ai sassi.": {
"file": "andrea-f2902ed3cd.mp3",
"sec": 3.72
},
"andrea|Una casa con la siepe rossa davanti. Da quel balcone si vede tutta la Carza.": {
"file": "andrea-4d4c45e5de.mp3",
"sec": 6.26
},
"andrea|La Carza sotto il muretto, con le canne dritte nell'acqua e il muro vecchio di sassi.": {
"file": "andrea-bcce90bf03.mp3",
"sec": 5.65
},
"andrea|Gli orti fatti coi bancali e le cassette, il bidone blu per l'acqua. Ognuno a modo suo.": {
"file": "andrea-5c7774bb32.mp3",
"sec": 7.15
},
"andrea|Qui la Carza ha un po' d'acqua chiara fra i sassi bianchi.": {
"file": "andrea-0a363e408f.mp3",
"sec": 3.63
},
"andrea|Il ponte delle poste, coi suoi tre archi di mattoni: da sotto pare più vecchio del paese.": {
"file": "andrea-1e782b31f7.mp3",
"sec": 5.87
},
"andrea|Altri orti, coi cavoli grossi come palloni.": {
"file": "andrea-dc196f39fc.mp3",
"sec": 3.41
},
"andrea|La Carza passa sotto gli archi e se ne va verso la Sieve.": {
"file": "andrea-33a6033e4f.mp3",
"sec": 3.16
},
"andrea|La casa dello zio Mario, quella gialla al sole.": {
"file": "andrea-d641627592.mp3",
"sec": 3.18
},
"andrea|Gli orti dello zio Mario: pomodori sulle canne, insalata, cavoli. Tutto in fila come soldati.": {
"file": "andrea-9fd392ca78.mp3",
"sec": 7.46
},
"andrea|Un pomodoro dell'orto dello zio? Se se ne accorge, e se ne accorge, son dolori.": {
"file": "andrea-1f05d7a7da.mp3",
"sec": 6.16
},
"andrea|Laggiù la passerella coperta sulla Carza.": {
"file": "andrea-42e8b1e2b0.mp3",
"sec": 2.81
},
"andrea|Di là ci passo un'altra volta: prima il palio.": {
"file": "andrea-d38b6e49ef.mp3",
"sec": 3.49
},
"andrea|Le case coi balconi pieni di vasi: qui ogni finestra è un giardino.": {
"file": "andrea-ec81875c47.mp3",
"sec": 5.22
},
"andrea|Il palazzo di mattoni e intonaco, coi balconi in fila.": {
"file": "andrea-d32e1a2a87.mp3",
"sec": 3.45
},
"andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": { "andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": {
"file": "andrea-0a073729fd.mp3", "file": "andrea-0a073729fd.mp3",
"sec": 7.3 "sec": 7.3
+5
View File
@@ -18,6 +18,11 @@ Online su 50037.manzolo.it tutto fino agli effetti ambientali di Codex. Aperto,
## Dove siamo arrivati ## Dove siamo arrivati
- **Nuovi posti dalle foto di Andrea (29 ottobre, sfondi di Codex)**: Via della Compagnia (fra Via de' Medici, la
Pieve e Villa Adami), Via Cafaggio in tre tratti fino alla rotonda, la Provinciale sotto i platani, la passeggiata
sulla Carza (cascatella, orti, dietro le poste, sotto il ponte delle poste, gli orti dello zio Mario), la strada fra
il bivio della Provinciale e la stazione. Le strade vecchie passano ora da questi posti. Battute provvisorie
[DA ANDREA]; foto originali in `assets/raw/foto/`.
- **La cerbottana (29 ottobre)**: in piazza, la bottega di Pierin di Frilli sotto la tenda rossa; si compra lo stucco - **La cerbottana (29 ottobre)**: in piazza, la bottega di Pierin di Frilli sotto la tenda rossa; si compra lo stucco
verde e si tira al muro della piazza (`muro-cerbottana.png`, Codex). Motore `cerbottana.js`, disegno verde e si tira al muro della piazza (`muro-cerbottana.png`, Codex). Motore `cerbottana.js`, disegno
`cerbottana-render.js`: si mira, si tiene premuto per il fiato (giusto al 70%: poco cade sotto, troppo va sopra), il `cerbottana-render.js`: si mira, si tiene premuto per il fiato (giusto al 70%: poco cade sotto, troppo va sopra), il
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "leggenda-del-regolo", "name": "leggenda-del-regolo",
"version": "0.1.47", "version": "0.1.52",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+113
View File
@@ -151,4 +151,117 @@ export const ambience = {
{ type: 'cloudshadow', phase: day, area: [60, 220, 520, 140], offset: 23000, { type: 'cloudshadow', phase: day, area: [60, 220, 520, 140], offset: 23000,
clip: [[0, 215], [400, 195], [640, 260], [640, 360], [0, 360]] }, clip: [[0, 215], [400, 195], [640, 260], [640, 360], [0, 360]] },
], ],
// I posti nuovi del 29 ottobre (foto di Andrea): nuvole, uccelli, la Carza che scorre e luccica, fronde, ombre.
via_compagnia: [
{ type: 'clouds', area: [130, 0, 340, 80], count: 2 },
{ type: 'lamps', phase: 'sera', points: [[115, 104]], glow: 10 },
bird([250, 300, 70, 16], 9000),
],
via_cafaggio: [
// Il panorama: Monte Cacioli e le colline, con le ombre delle nuvole che ci scorrono sopra.
{ type: 'clouds', area: [0, 0, 640, 80], count: 4, big: true },
{ type: 'cloudshadow', phase: day, area: [0, 80, 640, 90], offset: 6000, clip: rect([0, 80, 640, 90]) },
{ type: 'cloudshadow', phase: day, area: [0, 80, 640, 90], offset: 32000, clip: rect([0, 80, 640, 90]) },
{ type: 'birds', phase: day, area: [80, 20, 420, 70], count: 4, every: 21000, duration: 6000, offset: 3000 },
{ type: 'sway', rects: [[560, 0, 80, 90]], amount: 2 },
butterfly([40, 300, 160, 40], 15000),
],
via_cafaggio_fondo: [
{ type: 'clouds', area: [0, 10, 380, 80], count: 3, big: true },
{ type: 'birds', phase: day, area: [150, 20, 250, 60], count: 2, every: 26000, duration: 5000, offset: 8000 },
{ type: 'sway', rects: [[390, 0, 150, 160]], amount: 1.5 },
shade([300, 270, 220, 70], 12),
{ type: 'lamps', phase: 'sera', points: [[252, 83], [319, 121]], glow: 9 },
],
via_cafaggio_avanti: [
{ type: 'clouds', area: [150, 10, 400, 80], count: 3, big: true },
{ type: 'sway', rects: [[400, 60, 200, 90]], amount: 1.5 },
shade([180, 250, 260, 90], 12),
bird([120, 300, 80, 16], 14000),
],
rotonda_cafaggio: [
{ type: 'clouds', area: [200, 20, 420, 90], count: 4, big: true },
{ type: 'birds', phase: day, area: [250, 20, 300, 60], count: 3, every: 23000, duration: 5500, offset: 5000 },
{ type: 'sway', rects: [[0, 0, 140, 240], [350, 110, 200, 100], [560, 0, 80, 230]], amount: 2 },
shade([0, 250, 200, 60], 12),
],
provinciale_luna: [
{ type: 'clouds', area: [80, 20, 330, 80], count: 3, big: true },
{ type: 'birds', phase: day, area: [100, 20, 280, 60], count: 3, every: 24000, duration: 5000, offset: 12000 },
{ type: 'sway', rects: [[400, 0, 240, 170]], amount: 2 },
shade([380, 230, 170, 100], 14),
],
passeggiata_carza: [
// La cascatella che si vede dalla Luna: l'acqua che scende luccica, e la Carza scorre verso valle.
{ type: 'ripples', area: [150, 200, 196, 55], count: 14, flow: [-10, 4],
clip: [[150, 242], [210, 226], [290, 204], [346, 200], [346, 212], [300, 224], [225, 246], [160, 256]] },
{ type: 'sparkle', area: [296, 184, 46, 18], count: 5 },
{ type: 'sparkle', area: [170, 222, 120, 26], count: 3 },
{ type: 'clouds', area: [380, 40, 140, 50], count: 2 },
{ type: 'sway', rects: [[0, 0, 180, 250], [300, 0, 100, 200], [440, 60, 100, 120]], amount: 2 },
shade([200, 240, 300, 60], 14),
{ type: 'insect', kind: 'dragonfly', phase: day, area: [180, 214, 110, 30], every: 33000, duration: 9000, offset: 4000 },
butterfly([380, 228, 120, 40], 17000),
{ type: 'lamps', phase: 'sera', points: [[519, 77]], glow: 10 },
],
passeggiata_carza_avanti: [
{ type: 'ripples', area: [480, 236, 160, 70], count: 10, flow: [10, 4],
clip: [[480, 240], [560, 250], [640, 272], [640, 318], [560, 292], [500, 270]] },
{ type: 'sparkle', area: [520, 252, 100, 40], count: 3 },
{ type: 'clouds', area: [0, 0, 640, 90], count: 4, big: true },
{ type: 'birds', phase: day, area: [150, 20, 350, 60], count: 3, every: 22000, duration: 5500, offset: 7000 },
{ type: 'sway', rects: [[250, 10, 110, 170], [360, 40, 90, 120], [500, 60, 140, 120]], amount: 2 },
butterfly([40, 200, 160, 50], 9000),
],
passeggiata_poste: [
{ type: 'ripples', area: [470, 270, 170, 88], count: 12, flow: [10, 3],
clip: [[470, 278], [560, 272], [640, 300], [640, 358], [560, 358], [480, 302]] },
{ type: 'sparkle', area: [500, 290, 120, 50], count: 3 },
{ type: 'clouds', area: [380, 0, 260, 90], count: 3, big: true },
{ type: 'sway', rects: [[150, 50, 170, 150], [400, 120, 240, 90]], amount: 2 },
shade([180, 250, 130, 60], 10),
{ type: 'lamps', phase: 'sera', points: [[337, 144]], glow: 10 },
],
passeggiata_ponte: [
{ type: 'ripples', area: [480, 250, 160, 108], count: 12, flow: [10, 4],
clip: [[480, 256], [560, 250], [640, 272], [640, 358], [580, 358], [500, 300]] },
{ type: 'sparkle', area: [500, 270, 120, 70], count: 4 },
{ type: 'clouds', area: [240, 20, 300, 80], count: 3, big: true },
{ type: 'birds', phase: day, area: [250, 30, 250, 50], count: 2, every: 26000, duration: 5000, offset: 16000 },
{ type: 'sway', rects: [[0, 0, 250, 190], [270, 80, 370, 160]], amount: 2 },
butterfly([60, 220, 100, 40], 11000),
],
ponte_stazione: [
{ type: 'ripples', area: [300, 214, 340, 102], count: 18, flow: [12, 4],
clip: [[300, 218], [380, 214], [520, 240], [640, 280], [640, 316], [560, 300], [420, 252], [320, 228]] },
{ type: 'sparkle', area: [420, 240, 200, 60], count: 5 },
{ type: 'clouds', area: [180, 10, 280, 80], count: 3, big: true },
{ type: 'sway', rects: [[0, 0, 170, 190], [390, 0, 250, 210]], amount: 2 },
butterfly([150, 240, 150, 50], 6000),
{ type: 'insect', kind: 'dragonfly', phase: day, area: [440, 228, 110, 30], every: 31000, duration: 8500, offset: 15000 },
],
orti_zio_mario: [
{ type: 'clouds', area: [80, 20, 500, 90], count: 3, big: true },
{ type: 'birds', phase: day, area: [300, 30, 250, 60], count: 3, every: 24000, duration: 5000, offset: 2000 },
{ type: 'sway', rects: [[180, 10, 140, 180], [430, 10, 210, 240]], amount: 2 },
butterfly([10, 240, 140, 50], 8000),
bird([280, 290, 80, 18], 13000, 'blackbird'),
{ type: 'lamps', phase: 'sera', points: [[352, 110]], glow: 10 },
],
viale_stazione: [
{ type: 'clouds', area: [230, 0, 380, 110], count: 4, big: true },
{ type: 'birds', phase: day, area: [250, 20, 300, 60], count: 3, every: 22000, duration: 5500, offset: 9000 },
{ type: 'sway', rects: [[270, 110, 90, 110], [330, 160, 140, 70]], amount: 1.5 },
{ type: 'lamps', phase: 'sera', points: [[396, 117], [405, 155]], glow: 9 },
bird([520, 260, 60, 16], 18000),
],
terrazza: [
// Il panorama di casa: il paese e la Fortezza sotto un cielo che si muove, le ombre delle nuvole sulla collina.
{ type: 'clouds', area: [0, 0, 640, 80], count: 5, big: true },
{ type: 'cloudshadow', phase: day, area: [0, 40, 640, 200], offset: 4000, clip: rect([0, 40, 640, 200]) },
{ type: 'cloudshadow', phase: day, area: [0, 40, 640, 200], offset: 30000, clip: rect([0, 40, 640, 200]) },
{ type: 'birds', phase: day, area: [60, 20, 480, 80], count: 5, every: 19000, duration: 6500, offset: 1000 },
{ type: 'sway', rects: [[54, 176, 136, 100]], amount: 1.5 },
butterfly([500, 230, 100, 40], 10000),
],
}; };
+421 -16
View File
@@ -790,7 +790,7 @@ export const story = {
id: 'verso_forno', name: 'a destra, verso il forno', box: [570, 176, 70, 76], at: [588, 250], exit: { scene: 'forno', at: [628, 300], face: 'left' }, id: 'verso_forno', name: 'a destra, verso il forno', box: [570, 176, 70, 76], at: [588, 250], exit: { scene: 'forno', at: [628, 300], face: 'left' },
}, },
{ {
id: 'salita_villa_adami', name: 'salita verso Villa Adami', box: [440, 330, 200, 30], at: [600, 350], exit: { scene: 'villa_adami', at: [595, 268], face: 'left' }, id: 'salita_villa_adami', name: 'su per Via della Compagnia, verso Villa Adami', box: [440, 330, 200, 30], at: [600, 350], exit: { scene: 'via_compagnia', at: [94, 316], face: 'right' },
}, },
], ],
}, },
@@ -978,7 +978,7 @@ export const story = {
}, },
}, },
{ {
id: 'su_villa_adami', name: 'su verso Villa Adami', box: [272, 186, 60, 34], at: [300, 216], exit: { scene: 'villa_adami', at: [50, 252], face: 'right' }, id: 'su_villa_adami', name: 'su, verso Via della Compagnia', box: [272, 186, 60, 34], at: [300, 216], exit: { scene: 'via_compagnia', at: [300, 350], face: 'up' },
}, },
{ {
id: 'giu_piazzetta', name: 'giù verso Piazzetta Cambray Digny', box: [0, 334, 545, 26], at: [300, 354], exit: { scene: 'piazzetta', at: [20, 320], face: 'right' }, id: 'giu_piazzetta', name: 'giù verso Piazzetta Cambray Digny', box: [0, 334, 545, 26], at: [300, 354], exit: { scene: 'piazzetta', at: [20, 320], face: 'right' },
@@ -1318,6 +1318,13 @@ export const story = {
{ {
id: 'alla_stazione', name: 'verso la stazione', box: [100, 110, 80, 130], at: [137, 240], exit: { scene: 'stazione', at: [628, 320], face: 'left' }, id: 'alla_stazione', name: 'verso la stazione', box: [100, 110, 80, 130], at: [137, 240], exit: { scene: 'stazione', at: [628, 320], face: 'left' },
}, },
{
// Dietro le poste passa la passeggiata sulla Carza (Andrea, 29 ottobre).
id: 'alla_carza', name: 'dietro, la passeggiata sulla Carza', box: [560, 150, 80, 80], at: [600, 250], exit: { scene: 'passeggiata_poste', at: [280, 240], face: 'right' },
},
{
id: 'alla_strada', name: 'indietro, sulla strada della stazione', box: [100, 336, 440, 24], at: [300, 352], exit: { scene: 'viale_stazione', at: [620, 280], face: 'left' },
},
], ],
}, },
// Via Calimara: dalla Piazza scende alla farmacia, in Via Provinciale. // Via Calimara: dalla Piazza scende alla farmacia, in Via Provinciale.
@@ -1950,6 +1957,10 @@ export const story = {
{ {
id: 'alla_pieve', name: 'a destra, verso la Pieve', box: [590, 220, 50, 140], at: [628, 300], exit: { scene: 'pieve', at: [588, 250], face: 'left' }, id: 'alla_pieve', name: 'a destra, verso la Pieve', box: [590, 220, 50, 140], at: [628, 300], exit: { scene: 'pieve', at: [588, 250], face: 'left' },
}, },
{
// Dietro, su per l'arco di Via della Compagnia (Andrea, 29 ottobre).
id: 'all_arco', name: 'dietro, su per l\'arco di Via della Compagnia', box: [60, 334, 520, 26], at: [320, 350], exit: { scene: 'via_compagnia', at: [222, 296], face: 'up' },
},
], ],
}, },
stazione: { stazione: {
@@ -1977,11 +1988,15 @@ export const story = {
id: 'al_ponticino', name: 'a sinistra, verso il ponticino della Carza', box: [0, 190, 56, 120], at: [14, 300], exit: { scene: 'ponticino', at: [474, 202], face: 'down' }, id: 'al_ponticino', name: 'a sinistra, verso il ponticino della Carza', box: [0, 190, 56, 120], at: [14, 300], exit: { scene: 'ponticino', at: [474, 202], face: 'down' },
}, },
{ {
id: 'al_bivio', name: 'indietro, verso la Provinciale', box: [60, 334, 520, 26], at: [320, 354], exit: { scene: 'bivio_provinciale', at: [580, 345], face: 'left' }, id: 'al_bivio', name: 'indietro, verso la Provinciale', box: [60, 334, 520, 26], at: [320, 354], exit: { scene: 'viale_stazione', at: [340, 236], face: 'down' },
}, },
{ {
id: 'ai_giardini', name: 'a destra, il campone delle poste', box: [590, 190, 50, 100], at: [628, 300], exit: { scene: 'giardini_poste', at: [137, 240], face: 'down' }, id: 'ai_giardini', name: 'a destra, il campone delle poste', box: [590, 190, 50, 100], at: [628, 300], exit: { scene: 'giardini_poste', at: [137, 240], face: 'down' },
}, },
{
// Dal ponte delle poste si scende sulla passeggiata della Carza (Andrea, 29 ottobre).
id: 'giu_carza', name: 'giù dal ponte, la passeggiata sulla Carza', box: [590, 296, 50, 38], at: [620, 320], exit: { scene: 'ponte_stazione', at: [440, 252], face: 'left' },
},
], ],
}, },
// Via Provinciale fra il forno e la stazione: qui si sbuca dalla scuola. // Via Provinciale fra il forno e la stazione: qui si sbuca dalla scuola.
@@ -2009,7 +2024,7 @@ export const story = {
id: 'al_forno', name: 'laggiù, verso il forno', box: [280, 196, 80, 40], at: [318, 224], exit: { scene: 'forno', at: [12, 300], face: 'right' }, id: 'al_forno', name: 'laggiù, verso il forno', box: [280, 196, 80, 40], at: [318, 224], exit: { scene: 'forno', at: [12, 300], face: 'right' },
}, },
{ {
id: 'alla_stazione', name: 'a destra, la stazione', box: [560, 250, 80, 110], at: [580, 345], exit: { scene: 'stazione', at: [320, 350], face: 'up' }, id: 'alla_stazione', name: 'a destra, verso la stazione', box: [560, 250, 80, 110], at: [580, 345], exit: { scene: 'viale_stazione', at: [300, 352], face: 'up' },
}, },
], ],
}, },
@@ -2024,7 +2039,7 @@ export const story = {
entry: [300, 350], entry: [300, 350],
hotspots: [ hotspots: [
{ {
id: 'verso_la_sieve', name: 'avanti, verso La Luna', box: [392, 220, 80, 22], at: [435, 236], exit: { scene: 'verso_luna', at: [150, 206], face: 'down' }, id: 'verso_la_sieve', name: 'avanti, alla rotonda: Via Cafaggio e La Luna', box: [392, 220, 80, 22], at: [435, 236], exit: { scene: 'rotonda_cafaggio', at: [20, 245], face: 'right' },
}, },
{ {
id: 'negozi', name: 'negozi', box: [20, 150, 200, 95], at: [150, 330], face: 'left', id: 'negozi', name: 'negozi', box: [20, 150, 200, 95], at: [150, 330], face: 'left',
@@ -2090,7 +2105,7 @@ export const story = {
}, },
}, },
{ {
id: 'al_viale', name: 'indietro, verso il paese', box: [0, 182, 240, 26], at: [150, 200], exit: { scene: 'provinciale_viale', at: [435, 238], face: 'down' }, id: 'al_viale', name: 'indietro, verso la rotonda di Via Cafaggio', box: [0, 182, 240, 26], at: [150, 200], exit: { scene: 'provinciale_luna', at: [200, 352], face: 'up' },
}, },
], ],
}, },
@@ -2456,14 +2471,15 @@ export const story = {
}, },
}, },
{ {
id: 'verso_piazza', name: 'Via de\' Medici, verso la Piazza', box: [20, 215, 50, 40], at: [45, 250], exit: { scene: 'via_de_medici', at: [300, 216], face: 'down' }, id: 'verso_compagnia', name: 'a destra, giù per Via della Compagnia', box: [560, 236, 60, 38], at: [600, 266], exit: { scene: 'via_compagnia', at: [440, 258], face: 'down' },
},
{
id: 'verso_pieve', name: 'discesa verso la Pieve', box: [560, 236, 60, 38], at: [600, 266], exit: { scene: 'pieve', at: [600, 350], face: 'left' },
}, },
{ {
id: 'salita', name: 'salita verso la Fortezza', box: [100, 330, 400, 30], at: [300, 354], exit: { scene: 'salita_villa_adami', at: [340, 352], face: 'right' }, id: 'salita', name: 'salita verso la Fortezza', box: [100, 330, 400, 30], at: [300, 354], exit: { scene: 'salita_villa_adami', at: [340, 352], face: 'right' },
}, },
{
// Oltre il muretto si scende alla scuola e a Via Cafaggio (Andrea, 29 ottobre).
id: 'alla_scuola', name: 'giù, verso la scuola e Via Cafaggio', box: [502, 296, 138, 64], at: [498, 345], exit: { scene: 'via_cafaggio', at: [150, 352], face: 'up' },
},
], ],
}, },
// Il Poggio, dalla salita del leone alla Fortezza: la stradina col muretto e tutto il Mugello sotto (foto di Andrea, 24 ottobre). // Il Poggio, dalla salita del leone alla Fortezza: la stradina col muretto e tutto il Mugello sotto (foto di Andrea, 24 ottobre).
@@ -2529,7 +2545,7 @@ export const story = {
}, },
}, },
{ {
id: 'verso_scuola', name: 'strada verso la scuola', box: [0, 286, 120, 44], at: [40, 336], face: 'left', exit: { scene: 'scuola', at: [128, 340], face: 'right' }, id: 'verso_scuola', name: 'giù, verso la scuola e Via Cafaggio', box: [0, 286, 120, 44], at: [40, 336], face: 'left', exit: { scene: 'via_cafaggio', at: [300, 350], face: 'up' },
actions: { actions: {
look: [{ say: 'Di là si va alla scuola. Ci ho passato abbastanza anni, grazie.' }], look: [{ say: 'Di là si va alla scuola. Ci ho passato abbastanza anni, grazie.' }],
}, },
@@ -2755,11 +2771,11 @@ export const story = {
}, },
}, },
{ {
// «Di chi è quell'Audi nera?» «Let it be» (che si legge «L'è di Bi»: è di Bini). Andrea, 28 ottobre. // «Di chi è quell'Audi lì?» «Let it be» (che si legge «L'è di Bi»: è di Bini). Andrea, 28 ottobre.
id: 'audi', name: 'Audi nera', box: [292, 198, 68, 40], at: [330, 254], face: 'up', default: 'look', id: 'audi', name: 'Audi nera', box: [292, 198, 68, 40], at: [330, 254], face: 'up', default: 'look',
actions: { actions: {
look: [{ say: 'Di chi è quell\'Audi nera?' }, { say: 'Let it be.', who: 'arcu', pose: 'explain' }], look: [{ say: 'Di chi è quell\'Audi lì?' }, { say: 'Let it be.', who: 'arcu', pose: 'explain' }],
talk: [{ say: 'Di chi è quell\'Audi nera?' }, { say: 'Let it be.', who: 'arcu', pose: 'explain' }], talk: [{ say: 'Di chi è quell\'Audi lì?' }, { say: 'Let it be.', who: 'arcu', pose: 'explain' }],
use: [{ say: 'Non è mica mia. Mi garba, però, nera fiammante.' }], use: [{ say: 'Non è mica mia. Mi garba, però, nera fiammante.' }],
}, },
}, },
@@ -2793,7 +2809,7 @@ export const story = {
}, },
{ {
// Il campo del Giannini è laggiù a destra, oltre la siepe (Andrea, 28 ottobre). // Il campo del Giannini è laggiù a destra, oltre la siepe (Andrea, 28 ottobre).
id: 'al_giannini', name: 'verso il campo del Giannini', box: [404, 150, 58, 50], at: [520, 306], exit: { scene: 'campo_giannini', at: [60, 250], face: 'right' }, id: 'al_giannini', name: 'verso il campo del Giannini', box: [404, 150, 160, 42], at: [520, 306], exit: { scene: 'campo_giannini', at: [60, 250], face: 'right' },
}, },
// In fondo all'elenco, così sta sopra all'uscita e alla siepe. // In fondo all'elenco, così sta sopra all'uscita e alla siepe.
{ {
@@ -2917,6 +2933,395 @@ export const story = {
}, },
], ],
}, },
// Via della Compagnia (foto di Andrea, 29 ottobre): la strada che sale dalla Pieve verso la Fortezza. Alle spalle
// Via de' Medici, a sinistra l'arco che scende alla Pieve, a destra si sale a Villa Adami. [DA ANDREA: le battute]
via_compagnia: {
name: 'Via della Compagnia',
map: [34, 30],
background: 'assets/scenes/via-della-compagnia.png',
walk: [[0, 360], [0, 318], [88, 306], [200, 292], [250, 290], [330, 272], [420, 254], [446, 248], [460, 262], [540, 360]],
depth: [[248, 0.55], [360, 1.3]],
entry: [300, 350],
hotspots: [
{
id: 'stemma', name: 'stemma di pietra', box: [358, 160, 22, 18], at: [370, 266], face: 'up',
actions: { look: [{ say: 'Un piccolo stemma di pietra sul muro. Chissà di chi era, questa casa, un tempo.' }] },
},
{
id: 'lampione', name: 'lampione', box: [102, 90, 26, 36], at: [150, 306], face: 'up',
actions: { look: [{ say: 'Il lampione di ferro, attaccato al muro. La sera fa una luce gialla che pare d\'altri tempi.' }] },
},
{
// L'arco porta al forno; alla Pieve si scende fra le due case gialle (Andrea, 29 ottobre).
id: 'arco_forno', name: 'sotto l\'arco, giù al forno', box: [198, 222, 50, 66], at: [222, 296], exit: { scene: 'forno', at: [320, 350], face: 'up' },
},
{
id: 'alla_pieve', name: 'fra le case gialle, giù alla Pieve', box: [80, 196, 40, 100], at: [94, 316], exit: { scene: 'pieve', at: [600, 350], face: 'left' },
},
{
id: 'su_villa_adami', name: 'su verso Villa Adami', box: [420, 196, 42, 56], at: [440, 258], exit: { scene: 'villa_adami', at: [600, 266], face: 'left' },
},
{
id: 'giu_medici', name: 'giù per Via de\' Medici', box: [0, 336, 540, 24], at: [300, 352], exit: { scene: 'via_de_medici', at: [300, 216], face: 'down' },
},
],
},
// Via Cafaggio (foto di Andrea, 29 ottobre): scendendo dalla salita di Villa Adami (alle spalle), a sinistra il
// cancello della scuola con la palestra dal tetto a denti, dritto Via Cafaggio, lassù Monte Cacioli con le antenne.
// [DA ANDREA: le battute]
via_cafaggio: {
name: 'Via Cafaggio',
map: [26, 17],
background: 'assets/scenes/via-cafaggio.png',
walk: [[0, 360], [0, 346], [160, 318], [300, 268], [345, 258], [400, 240], [470, 208], [500, 188], [515, 188], [530, 230], [555, 300], [565, 360]],
depth: [[188, 0.3], [360, 1.4]],
entry: [300, 350],
hotspots: [
{
id: 'palestra', name: 'palestra della scuola', box: [20, 165, 250, 60], at: [200, 324], face: 'up',
actions: { look: [{ say: 'La palestra della scuola, col tetto a denti di sega.' }] },
},
{
id: 'monte_cacioli', name: 'Monte Cacioli', box: [230, 86, 170, 56], at: [300, 300], face: 'up',
actions: { look: [{ say: 'Lassù Monte Cacioli, con le antenne in cima.' }] },
},
{
id: 'giu_cafaggio', name: 'dritto, giù per Via Cafaggio', box: [486, 166, 46, 36], at: [507, 192], exit: { scene: 'via_cafaggio_fondo', at: [300, 350], face: 'up' },
},
{
id: 'cancello', name: 'cancello della palestra', box: [298, 188, 52, 70], at: [322, 268], face: 'up',
actions: { look: [{ say: 'Il cancello della palestra. Per la scuola si passa dalla stradina più in là.' }], open: [{ say: 'Chiuso. Per la scuola si passa dalla stradina più in là.' }] },
},
{
// Alla scuola non si passa dal cancello ma dalla stradina subito dopo (Andrea, 29 ottobre).
id: 'alla_scuola', name: 'la stradina della scuola', box: [380, 172, 50, 32], at: [410, 246], exit: { scene: 'scuola', at: [128, 340], face: 'right' },
},
{
id: 'su_villa', name: 'su, verso Villa Adami', box: [0, 336, 280, 24], at: [150, 352], exit: { scene: 'villa_adami', at: [498, 345], face: 'up' },
},
{
id: 'su_salita', name: 'su per la salita, verso la Fortezza', box: [280, 336, 280, 24], at: [400, 352], exit: { scene: 'salita_villa_adami', at: [40, 336], face: 'right' },
},
],
},
// Via Cafaggio più giù (foto di Andrea, 29 ottobre): dritta fra le case, verso la Provinciale. [DA ANDREA: battute]
via_cafaggio_fondo: {
name: 'Via Cafaggio',
map: [44, 40],
background: 'assets/scenes/via-cafaggio-fondo.png',
walk: [[60, 360], [150, 300], [260, 240], [340, 196], [368, 176], [386, 176], [400, 200], [420, 262], [470, 282], [640, 318], [640, 360]],
depth: [[176, 0.3], [360, 1.4]],
entry: [300, 350],
hotspots: [
{
id: 'siepe', name: 'siepe', box: [400, 0, 240, 260], at: [430, 290], face: 'right',
actions: { look: [{ say: 'Una siepe alta come una casa, e l\'edera che si mangia il muro.' }] },
},
{
id: 'villette', name: 'villette', box: [0, 30, 200, 130], at: [200, 300], face: 'left',
actions: { look: [{ say: 'Le villette col giardino e il muretto: qui è tutto in ordine.' }] },
},
{
id: 'avanti_cafaggio', name: 'avanti per Via Cafaggio', box: [350, 150, 56, 34], at: [377, 180], exit: { scene: 'via_cafaggio_avanti', at: [300, 350], face: 'up' },
},
{
id: 'su_cafaggio', name: 'indietro, su per Via Cafaggio', box: [0, 336, 640, 24], at: [300, 352], exit: { scene: 'via_cafaggio', at: [507, 192], face: 'down' },
},
],
},
// Via Cafaggio, ancora avanti (foto di Andrea, 29 ottobre: Codex ha ricostruito il pezzo coperto dal dito).
via_cafaggio_avanti: {
name: 'Via Cafaggio, verso la rotonda',
map: [56, 58],
background: 'assets/scenes/via-cafaggio-avanti.png',
walk: [[0, 360], [0, 330], [110, 268], [230, 190], [248, 176], [272, 176], [300, 200], [360, 262], [440, 330], [460, 360]],
depth: [[176, 0.3], [360, 1.4]],
entry: [300, 350],
hotspots: [
{
id: 'villa_archi', name: 'villa con gli archi', box: [470, 40, 170, 90], at: [380, 300], face: 'right',
actions: { look: [{ say: 'Una villa con gli archi dietro la siepe. Qui le siepi le tosano col righello.' }] },
},
{
id: 'alla_rotonda', name: 'in fondo, alla rotonda della Provinciale', box: [236, 158, 50, 34], at: [260, 180], exit: { scene: 'rotonda_cafaggio', at: [390, 352], face: 'up' },
},
{
id: 'su_cafaggio', name: 'indietro, su per Via Cafaggio', box: [0, 336, 460, 24], at: [300, 352], exit: { scene: 'via_cafaggio_fondo', at: [377, 180], face: 'down' },
},
],
},
// In fondo a Via Cafaggio, la rotonda sulla Provinciale (foto di Andrea, 29 ottobre): a sinistra il viale
// alberato, dritto verso La Luna, lungo la Carza.
rotonda_cafaggio: {
name: 'La rotonda in fondo a Via Cafaggio',
map: [64, 66],
background: 'assets/scenes/rotonda-cafaggio.png',
walk: [[180, 360], [230, 320], [300, 290], [330, 262], [0, 252], [0, 238], [360, 236], [520, 224], [560, 224], [590, 300], [600, 360]],
depth: [[224, 0.4], [360, 1.35]],
entry: [390, 352],
hotspots: [
{
id: 'cipresso', name: 'cipresso', box: [146, 0, 64, 250], at: [300, 292], face: 'left',
actions: { look: [{ say: 'Un cipresso dritto come un fuso, in mezzo all\'aiuola.' }] },
},
{
id: 'panchina', name: 'panchina', box: [68, 252, 110, 40], at: [260, 336], face: 'left',
actions: { look: [{ say: 'Una panchina all\'ombra.' }], use: [{ say: 'Sedersi ora? Prima il palio.' }] },
},
{
id: 'palazzo_rosso', name: 'palazzo di mattoni', box: [204, 136, 146, 88], at: [360, 240], face: 'up',
actions: { look: [{ say: 'Il palazzo di mattoni coi negozi sotto, di là dalla Provinciale.' }] },
},
{
id: 'al_viale', name: 'a sinistra, il viale alberato', box: [0, 222, 60, 40], at: [20, 245], exit: { scene: 'provinciale_viale', at: [435, 238], face: 'down' },
},
{
id: 'verso_luna', name: 'dritto, lungo la Provinciale verso La Luna', box: [500, 204, 60, 30], at: [540, 226], exit: { scene: 'provinciale_luna', at: [330, 198], face: 'down' },
},
{
id: 'su_cafaggio', name: 'indietro, su per Via Cafaggio', box: [180, 336, 420, 24], at: [390, 352], exit: { scene: 'via_cafaggio_avanti', at: [260, 180], face: 'down' },
},
],
},
// La Provinciale fra La Luna (alle spalle) e la rotonda di Via Cafaggio (davanti); a destra, fra i platani, la
// passeggiata sulla Carza (foto di Andrea, 29 ottobre). [DA ANDREA: le battute]
provinciale_luna: {
name: 'La Provinciale, sotto i platani',
map: [65, 70],
background: 'assets/scenes/provinciale-luna.png',
walk: [[0, 360], [0, 262], [150, 222], [290, 200], [380, 192], [420, 192], [470, 200], [505, 240], [510, 268], [400, 268], [390, 360]],
depth: [[190, 0.35], [360, 1.4]],
entry: [200, 352],
hotspots: [
{
id: 'platani', name: 'platani', box: [530, 0, 110, 360], at: [470, 262], face: 'right',
actions: { look: [{ say: 'I platani della Provinciale: d\'estate fanno un\'ombra che pare una galleria.' }] },
},
{
id: 'villette', name: 'villette', box: [0, 70, 250, 110], at: [150, 300], face: 'left',
actions: { look: [{ say: 'Le villette sotto il bosco, coi giardini curati.' }] },
},
{
id: 'alla_rotonda', name: 'avanti, alla rotonda di Via Cafaggio', box: [290, 176, 90, 24], at: [330, 198], exit: { scene: 'rotonda_cafaggio', at: [540, 226], face: 'left' },
},
{
id: 'alla_passeggiata', name: 'a destra, la passeggiata sulla Carza', box: [462, 186, 48, 44], at: [480, 232], exit: { scene: 'passeggiata_carza', at: [330, 352], face: 'up' },
},
{
id: 'alla_luna', name: 'indietro, verso La Luna', box: [0, 336, 390, 24], at: [200, 352], exit: { scene: 'verso_luna', at: [150, 206], face: 'down' },
},
],
},
// La passeggiata sulla Carza (foto di Andrea, 29 ottobre): il prato, il greto e la cascatella che si vede dalla
// Luna. [DA ANDREA: le battute]
passeggiata_carza: {
name: 'La passeggiata sulla Carza',
map: [70, 76],
background: 'assets/scenes/passeggiata-carza.png',
walk: [[0, 360], [0, 300], [190, 300], [230, 270], [330, 235], [420, 200], [540, 195], [560, 260], [520, 300], [540, 360]],
depth: [[195, 0.45], [360, 1.35]],
entry: [330, 352],
hotspots: [
{
id: 'cascata', name: 'la cascatella', box: [288, 172, 84, 36], at: [330, 246], face: 'left',
actions: { look: [{ say: 'La cascatella della Carza: quella che si vede anche dalla Luna.' }, { say: 'D\'estate si veniva a rinfrescare i piedi qui sotto.' }] },
},
{
id: 'greto', name: 'greto della Carza', box: [150, 186, 150, 70], at: [230, 280], face: 'left',
actions: { look: [{ say: 'Il greto pieno di sassi. Dighe, ponti, pesci immaginari: qui ci si passava l\'estate.' }] },
},
{
id: 'lampione', name: 'lampione', box: [504, 70, 26, 186], at: [480, 270], face: 'right',
actions: { look: [{ say: 'Un lampione in mezzo al prato. Di sera la passeggiata la fanno le zanzare.' }] },
},
{
// Il sentiero va avanti a sinistra, dietro il grande albero (Andrea, 29 ottobre).
id: 'avanti_carza', name: 'avanti, lungo la Carza', box: [0, 200, 50, 130], at: [20, 330], exit: { scene: 'passeggiata_carza_avanti', at: [300, 352], face: 'up' },
},
{
id: 'alla_provinciale', name: 'indietro, sulla Provinciale', box: [180, 336, 360, 24], at: [330, 352], exit: { scene: 'provinciale_luna', at: [480, 232], face: 'left' },
},
],
},
// La passeggiata sulla Carza più avanti (foto di Andrea, 29 ottobre): il ponticino, gli orti, il greto.
// [DA ANDREA: le battute]
passeggiata_carza_avanti: {
name: 'La passeggiata sulla Carza, gli orti',
map: [74, 80],
background: 'assets/scenes/passeggiata-carza-avanti.png',
walk: [[0, 360], [0, 300], [180, 236], [262, 186], [300, 186], [350, 222], [470, 262], [560, 300], [600, 360]],
depth: [[186, 0.4], [360, 1.4]],
entry: [300, 352],
hotspots: [
{
id: 'orti', name: 'orti', box: [0, 150, 220, 130], at: [150, 310], face: 'left',
actions: { look: [{ say: 'Gli orti con la rete: cavoli, insalata e i fagioli sulle canne.' }], take: [{ say: 'Un cavolo dall\'orto di qualcun altro? Poi chi lo sente.' }] },
},
{
id: 'ponticino', name: 'ponticino', box: [280, 182, 72, 40], at: [316, 232], face: 'up',
actions: { look: [{ say: 'Un ponticino di ferro sul fosso. Due passi e sei di là.' }] },
},
{
id: 'greto', name: 'greto della Carza', box: [470, 210, 170, 126], at: [520, 300], face: 'right',
actions: { look: [{ say: 'Il greto bianco della Carza, con un filo d\'acqua in mezzo ai sassi.' }] },
},
{
id: 'in_fondo', name: 'avanti, lungo la Carza', box: [252, 146, 56, 34], at: [282, 190], exit: { scene: 'passeggiata_poste', at: [250, 352], face: 'up' },
},
{
id: 'indietro', name: 'indietro, verso la cascatella', box: [0, 336, 600, 24], at: [300, 352], exit: { scene: 'passeggiata_carza', at: [20, 330], face: 'right' },
},
],
},
// La passeggiata sulla Carza, dietro le case (foto di Andrea, 29 ottobre): a sinistra si va al campone delle poste,
// dritto si continua verso il ponte delle poste. [DA ANDREA: le battute]
passeggiata_poste: {
name: 'La passeggiata sulla Carza, dietro le poste',
map: [16, 64],
background: 'assets/scenes/passeggiata-carza-poste.png',
walk: [[90, 360], [180, 300], [260, 250], [300, 224], [318, 224], [330, 250], [350, 300], [420, 360]],
depth: [[224, 0.45], [360, 1.4]],
entry: [250, 352],
hotspots: [
{
id: 'casa', name: 'casa con la siepe', box: [0, 0, 150, 170], at: [150, 330], face: 'left',
actions: { look: [{ say: 'Una casa con la siepe rossa davanti. Da quel balcone si vede tutta la Carza.' }] },
},
{
id: 'carza', name: 'la Carza', box: [440, 230, 200, 130], at: [370, 320], face: 'right',
actions: { look: [{ say: 'La Carza sotto il muretto, con le canne dritte nell\'acqua e il muro vecchio di sassi.' }] },
},
{
id: 'al_campone', name: 'a sinistra, il campone delle poste', box: [226, 196, 48, 40], at: [280, 240], exit: { scene: 'giardini_poste', at: [600, 250], face: 'left' },
},
{
id: 'avanti', name: 'avanti, verso il ponte delle poste', box: [286, 196, 52, 30], at: [310, 226], exit: { scene: 'passeggiata_ponte', at: [225, 352], face: 'up' },
},
{
id: 'indietro', name: 'indietro, verso gli orti', box: [90, 336, 330, 24], at: [250, 352], exit: { scene: 'passeggiata_carza_avanti', at: [282, 190], face: 'down' },
},
],
},
// L'ultimo pezzo della passeggiata sulla Carza (foto di Andrea, 29 ottobre): gli orti a sinistra, la Carza a destra,
// in fondo il ponte che collega le poste alla stazione. [DA ANDREA: le battute]
passeggiata_ponte: {
name: 'La passeggiata sulla Carza, verso il ponte',
map: [14, 70],
background: 'assets/scenes/passeggiata-carza-ponte.png',
walk: [[150, 360], [200, 290], [226, 200], [242, 200], [260, 280], [300, 360]],
depth: [[200, 0.4], [360, 1.4]],
entry: [225, 352],
hotspots: [
{
id: 'orti', name: 'orti', box: [0, 140, 170, 180], at: [190, 320], face: 'left',
actions: { look: [{ say: 'Gli orti fatti coi bancali e le cassette, il bidone blu per l\'acqua. Ognuno a modo suo.' }] },
},
{
id: 'carza', name: 'la Carza', box: [450, 210, 190, 150], at: [270, 330], face: 'right',
actions: { look: [{ say: 'Qui la Carza ha un po\' d\'acqua chiara fra i sassi bianchi.' }] },
},
{
id: 'al_ponte', name: 'avanti, al ponte delle poste', box: [212, 168, 46, 36], at: [234, 204], exit: { scene: 'ponte_stazione', at: [300, 352], face: 'up' },
},
{
id: 'indietro', name: 'indietro, dietro le poste', box: [150, 336, 150, 24], at: [225, 352], exit: { scene: 'passeggiata_poste', at: [310, 226], face: 'down' },
},
],
},
// In fondo alla passeggiata sulla Carza, sotto il ponte ad archi che collega le poste alla stazione (foto di Andrea,
// 29 ottobre): il viottolo a sinistra sale sul ponte. [DA ANDREA: le battute]
ponte_stazione: {
name: 'Sotto il ponte delle poste',
map: [12, 75],
background: 'assets/scenes/ponte-carza-stazione.png',
walk: [[0, 360], [0, 320], [60, 290], [120, 240], [150, 190], [168, 190], [200, 240], [300, 232], [420, 236], [520, 280], [640, 300], [640, 360]],
depth: [[190, 0.4], [360, 1.35]],
entry: [300, 352],
hotspots: [
{
id: 'ponte', name: 'il ponte ad archi', box: [190, 124, 200, 90], at: [300, 240], face: 'up',
actions: { look: [{ say: 'Il ponte delle poste, coi suoi tre archi di mattoni: da sotto pare più vecchio del paese.' }] },
},
{
id: 'orti', name: 'orti', box: [0, 180, 120, 90], at: [80, 300], face: 'left',
actions: { look: [{ say: 'Altri orti, coi cavoli grossi come palloni.' }] },
},
{
id: 'carza', name: 'la Carza', box: [440, 210, 200, 90], at: [460, 262], face: 'right',
actions: { look: [{ say: 'La Carza passa sotto gli archi e se ne va verso la Sieve.' }] },
},
{
// Il viottolo passa sotto l'arco e va avanti lungo la Carza; alla stazione si sale dalla parte destra del ponte
// (Andrea, 29 ottobre).
id: 'avanti_carza', name: 'avanti per il viottolo, sotto il ponte', box: [136, 158, 46, 34], at: [158, 192], exit: { scene: 'orti_zio_mario', at: [400, 352], face: 'up' },
},
{
id: 'su_ponte', name: 'su sul ponte, alla stazione', box: [410, 86, 100, 60], at: [440, 252], exit: { scene: 'stazione', at: [620, 320], face: 'left' },
},
{
id: 'indietro', name: 'indietro lungo la Carza', box: [0, 336, 640, 24], at: [300, 352], exit: { scene: 'passeggiata_ponte', at: [234, 204], face: 'down' },
},
],
},
// Oltre il ponte delle poste, lungo la Carza (foto di Andrea, 29 ottobre): la casa gialla al sole è quella dello zio
// Mario, e sotto ci sono i suoi orti. In fondo la passerella. [DA ANDREA: le battute]
orti_zio_mario: {
name: 'Gli orti dello zio Mario',
map: [8, 82],
background: 'assets/scenes/orti-zio-mario.png',
walk: [[150, 360], [300, 250], [280, 236], [340, 236], [420, 248], [640, 330], [640, 360]],
depth: [[236, 0.45], [360, 1.35]],
entry: [400, 352],
hotspots: [
{
id: 'casa_zio', name: 'casa dello zio Mario', box: [80, 90, 200, 90], at: [260, 290], face: 'left',
actions: { look: [{ say: 'La casa dello zio Mario, quella gialla al sole.' }] },
},
{
id: 'orti', name: 'gli orti dello zio Mario', box: [0, 180, 250, 170], at: [205, 330], face: 'left',
actions: {
look: [{ say: 'Gli orti dello zio Mario: pomodori sulle canne, insalata, cavoli. Tutto in fila come soldati.' }],
take: [{ say: 'Un pomodoro dell\'orto dello zio? Se se ne accorge, e se ne accorge, son dolori.' }],
},
},
{
id: 'passerella', name: 'la passerella sulla Carza', box: [384, 186, 70, 30], at: [410, 252], face: 'up',
actions: { look: [{ say: 'Laggiù la passerella coperta sulla Carza.' }], walk: [{ say: 'Di là ci passo un\'altra volta: prima il palio.' }] },
},
{
id: 'indietro', name: 'indietro, verso il ponte delle poste', box: [150, 336, 490, 24], at: [400, 352], exit: { scene: 'ponte_stazione', at: [158, 192], face: 'down' },
},
],
},
// La strada fra il bivio della Provinciale (alle spalle) e la stazione (in fondo); a destra si va al campone delle
// poste (foto di Andrea, 29 ottobre). [DA ANDREA: le battute]
viale_stazione: {
name: 'Verso la stazione',
map: [20, 52],
background: 'assets/scenes/viale-stazione.png',
walk: [[30, 360], [230, 280], [320, 240], [330, 228], [360, 228], [400, 250], [440, 262], [600, 268], [640, 290], [640, 360]],
depth: [[228, 0.35], [360, 1.4]],
entry: [300, 352],
hotspots: [
{
id: 'case', name: 'case coi balconi fioriti', box: [0, 0, 230, 270], at: [200, 320], face: 'left',
actions: { look: [{ say: 'Le case coi balconi pieni di vasi: qui ogni finestra è un giardino.' }] },
},
{
id: 'palazzo', name: 'palazzo di mattoni', box: [450, 20, 190, 220], at: [520, 280], face: 'right',
actions: { look: [{ say: 'Il palazzo di mattoni e intonaco, coi balconi in fila.' }] },
},
{
id: 'alla_stazione', name: 'avanti, la stazione', box: [320, 196, 50, 36], at: [340, 236], exit: { scene: 'stazione', at: [320, 350], face: 'up' },
},
{
id: 'al_campone', name: 'a destra, il campone delle poste', box: [590, 240, 50, 60], at: [620, 280], exit: { scene: 'giardini_poste', at: [300, 352], face: 'up' },
},
{
id: 'al_bivio', name: 'indietro, al bivio della Provinciale', box: [30, 336, 610, 24], at: [300, 352], exit: { scene: 'bivio_provinciale', at: [580, 345], face: 'left' },
},
],
},
// L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri. // L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri.
scuola: { scuola: {
name: 'La scuola', name: 'La scuola',
@@ -2959,7 +3364,7 @@ export const story = {
id: 'al_bivio', name: 'a destra, verso la Provinciale', box: [584, 220, 56, 120], at: [600, 320], exit: { scene: 'bivio_provinciale', at: [208, 226], face: 'right' }, id: 'al_bivio', name: 'a destra, verso la Provinciale', box: [584, 220, 56, 120], at: [600, 320], exit: { scene: 'bivio_provinciale', at: [208, 226], face: 'right' },
}, },
{ {
id: 'alla_salita', name: 'a sinistra, verso la salita', box: [0, 240, 130, 120], at: [128, 340], exit: { scene: 'salita_villa_adami', at: [40, 336], face: 'right' }, id: 'alla_salita', name: 'a sinistra, dal cancello verso Via Cafaggio', box: [0, 240, 130, 120], at: [128, 340], exit: { scene: 'via_cafaggio', at: [410, 246], face: 'down' },
}, },
], ],
}, },
+1 -1
View File
@@ -1,5 +1,5 @@
// La versione del gioco: la cambia scripts/bump.sh, insieme a package.json. // La versione del gioco: la cambia scripts/bump.sh, insieme a package.json.
export const VERSION = '0.1.47'; export const VERSION = '0.1.52';
// Immagini, musiche e voci con ?v=<versione>: a ogni versione l'indirizzo cambia e il browser le riscarica, // Immagini, musiche e voci con ?v=<versione>: a ogni versione l'indirizzo cambia e il browser le riscarica,
// mentre fra una versione e l'altra le tiene in cache a lungo (nginx.conf). // mentre fra una versione e l'altra le tiene in cache a lungo (nginx.conf).
+15
View File
@@ -67,3 +67,18 @@ python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/partita
- **Il ritaglio** (`TAGLIO` in `monta.py`) va rimisurato quando cambia l'impaginazione: `convert -trim` su una - **Il ritaglio** (`TAGLIO` in `monta.py`) va rimisurato quando cambia l'impaginazione: `convert -trim` su una
schermata della prova a vuoto. schermata della prova a vuoto.
- **Per Telegram** (50 MB al massimo): due passate x264 `-tune animation` a 470 kbit/s → circa 45 MB. - **Per Telegram** (50 MB al massimo): due passate x264 `-tune animation` a 470 kbit/s → circa 45 MB.
## Il trailer e la extended (29 ottobre 2026)
Copioni `video/trailer.mjs` (circa 1 min 30 s) e `video/trailer-extended.mjs` (circa 3 min 25 s, col giro completo
del paese e i giochi giocati da soli). Niente clic: la scena è a tutto schermo, le scritte e gli stacchi al nero
sono elementi aggiunti nella pagina, e la musica la sceglie il copione (`out/<clip>-audio.json`). Nel finale si
cercano solo gli occhi gialli: il palio non si mostra (Andrea: si capirebbe dov'è).
- Prova locale, senza VM: `OUT=/tmp/prova COPIONE=./trailer.mjs node video/prova-locale.mjs` (una schermata per
battuta, e i tempi).
- Ripresa: come la partita completa, poi `python3 video/monta.py <cartella> --trailer`.
- Il gioco va in pausa da solo dopo un minuto senza tocchi: il copione manda un finto `pointermove` ogni 10 s.
- Una camminata lascia `game.task` in sospeso, e cambiando scena di colpo il gioco non si ferma più: si azzera.
- Le battute saltate lasciano spezzoni di voce nel registro: prima di montare si tolgono quelli sotto il mezzo secondo
(attenzione a «Let it be.», che dura un secondo ed è giusta).
+9
View File
@@ -4,6 +4,7 @@ copione -> video/out/<clip>.mp4.
python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/demo-prototipo python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/demo-prototipo
python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/partita-completa python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/partita-completa
python3 video/monta.py ~/Workspaces/qemu/qemu-iso-lab/artifacts/tour/out/trailer --trailer
- ritaglia lo schermo 1600x900 della VM sull'area del gioco e lascia sopra una fascia per le didascalie; - ritaglia lo schermo 1600x900 della VM sull'area del gioco e lascia sopra una fascia per le didascalie;
- le didascalie (cues.json, solo «it») restano a schermo il tempo di leggerle, non fino alla successiva; - le didascalie (cues.json, solo «it») restano a schermo il tempo di leggerle, non fino alla successiva;
@@ -31,6 +32,9 @@ VOLUME = 0.5 # musica della demo-prototipo (senza voci)
VOLUME_SOTTO = 0.22 # musica sotto le voci: bassa (Andrea, 25 ottobre) VOLUME_SOTTO = 0.22 # musica sotto le voci: bassa (Andrea, 25 ottobre)
VOLUME_VOCI = 1.0 VOLUME_VOCI = 1.0
VOLUME_NARRATORE = 1.15 VOLUME_NARRATORE = 1.15
# Il trailer (--trailer): tutto lo schermo della VM (la scena è a tutto schermo), niente fascia né didascalie (le
# scritte sono nella pagina), la musica in primo piano.
VOLUME_TRAILER = 0.6
VOLUME_TUTORIAL = 0.07 # musica dei video «Come si gioca»: sottofondo # il narratore dei video «Come si gioca», sopra la musica bassa VOLUME_TUTORIAL = 0.07 # musica dei video «Come si gioca»: sottofondo # il narratore dei video «Come si gioca», sopra la musica bassa
FADE = 1.2 FADE = 1.2
@@ -49,6 +53,7 @@ def ts(s: float) -> str:
def main() -> None: def main() -> None:
d = Path(sys.argv[1]).expanduser() d = Path(sys.argv[1]).expanduser()
tutorial = "--tutorial" in sys.argv[2:] tutorial = "--tutorial" in sys.argv[2:]
trailer = "--trailer" in sys.argv[2:]
taglio, fascia = (TAGLIO_TUTORIAL, FASCIA_TUTORIAL) if tutorial else (TAGLIO, FASCIA) taglio, fascia = (TAGLIO_TUTORIAL, FASCIA_TUTORIAL) if tutorial else (TAGLIO, FASCIA)
meta = json.loads((d / "cues.json").read_text()) meta = json.loads((d / "cues.json").read_text())
nome = meta["clip"] nome = meta["clip"]
@@ -71,6 +76,8 @@ def main() -> None:
# «troppo alta la musica del gioco»). # «troppo alta la musica del gioco»).
if (OUT / f"{nome}-narrazione.json").exists(): if (OUT / f"{nome}-narrazione.json").exists():
volume = VOLUME_TUTORIAL volume = VOLUME_TUTORIAL
if trailer:
volume = VOLUME_TRAILER
else: else:
musica = json.loads((OUT / "musica.json").read_text()) musica = json.loads((OUT / "musica.json").read_text())
volume = VOLUME volume = VOLUME
@@ -131,6 +138,8 @@ def main() -> None:
mix = "".join(pezzi) + f"amix=inputs={len(pezzi)}:normalize=0:duration=longest[a]" mix = "".join(pezzi) + f"amix=inputs={len(pezzi)}:normalize=0:duration=longest[a]"
stile = f"FontName=DejaVu Sans,FontSize=11,Bold=1,MarginV=4,Outline=1,Shadow=0,PrimaryColour=&H007AD2FF" stile = f"FontName=DejaVu Sans,FontSize=11,Bold=1,MarginV=4,Outline=1,Shadow=0,PrimaryColour=&H007AD2FF"
video = f"[0:v]{taglio},pad=iw:ih+{fascia}:0:{fascia}:{FONDO},subtitles={srt}:force_style='{stile}'[v]" video = f"[0:v]{taglio},pad=iw:ih+{fascia}:0:{fascia}:{FONDO},subtitles={srt}:force_style='{stile}'[v]"
if trailer:
video = "[0:v]scale=1600:900:flags=lanczos,fade=t=in:d=0.5[v]"
finale = OUT / f"{nome}.mp4" finale = OUT / f"{nome}.mp4"
subprocess.run(["ffmpeg", "-loglevel", "error", "-y", "-i", str(raw), *ingressi, subprocess.run(["ffmpeg", "-loglevel", "error", "-y", "-i", str(raw), *ingressi,
"-filter_complex", ";".join([video, *catene, mix]), "-map", "[v]", "-map", "[a]", "-filter_complex", ";".join([video, *catene, mix]), "-map", "[v]", "-map", "[a]",
+19
View File
@@ -0,0 +1,19 @@
// Prova locale di un copione del trailer, senza la VM: un finto «d» col solo necessario, una schermata per battuta.
// OUT=/tmp/prova COPIONE=./trailer-extended.mjs node video/prova-locale.mjs
import { chromium } from 'playwright';
import { staticServer } from '../tests/helpers.mjs';
const server = await staticServer();
const browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
const out = process.env.OUT;
try {
const page = await browser.newPage({ viewport: { width: 1600, height: 900 } });
page.on('pageerror', e => console.log('ERR', e.message));
const orig = page.goto.bind(page);
page.goto = url => orig(url.replace('http://127.0.0.1:8000/', `http://127.0.0.1:${server.port}/`));
const mod = await import(process.env.COPIONE ?? './trailer.mjs');
const t0 = Date.now(); let n = 0;
const d = { page, vm: () => {}, cue: async id => { console.log(((Date.now() - t0) / 1000).toFixed(1), id); setTimeout(() => page.screenshot({ path: `${out}/tr-${String(++n).padStart(2, '0')}-${id}.png` }), id === 'buio' ? 5000 : 3000); } };
await mod.setup(d);
await mod.run(d);
console.log('fine', ((Date.now() - t0) / 1000).toFixed(1));
} finally { await browser.close(); server.kill(); }
+263
View File
@@ -0,0 +1,263 @@
// Copione del trailer lungo «La Leggenda del Regolo · extended» (scaletta approvata da Andrea il 29 ottobre): circa tre
// minuti, come il trailer (video/trailer.mjs) ma col giro completo del paese e i giochi giocati per qualche secondo:
// bocce, biliardo, GP di Fortezza, briscola, macchinine, cerbottana.
// Si gira come la partita completa (video/SCALETTA.md), poi: python3 video/monta.py <cartella> --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 · extended", en: "La Leggenda del Regolo · extended" };
export const cues = [
c("apertura", "San Piero a Sieve, maggio 2007"),
c("notte", "La notte prima del Palio…"),
c("sparito", "…il palio è sparito."),
c("giro", "il giro del paese"),
c("sfide", "Un paese pieno di sfide"),
c("bocce", "Le bocce al pallaio"),
c("biliardo", "Il biliardo del Circolo"),
c("gara", "Il GP di Fortezza"),
c("carte", "La briscola all'oratorio"),
c("macchinine", "Le macchinine col gesso"),
c("cerbottana", "La cerbottana"),
c("amici", "…e di vecchie conoscenze"),
c("buio", "la fessura"),
c("titolo", "La Leggenda del Regolo"),
];
const URL_GIOCO = "http://127.0.0.1:8000/";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
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])");
// Si entra in partita dietro le quinte: rione, introduzione saltata.
await p.click("#new-game");
await p.click('#rione [data-rione="campomigliaio"]');
await p.click("#skip-intro");
// Il palcoscenico del trailer: solo la scena, a tutto schermo; sopra, il nero e le scritte.
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; }
#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: 42px; line-height: 1.6; color: #ffe9b0; opacity: 0;
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: 32px; }
` });
await p.evaluate(() => {
for (const id of ["tr-nero", "tr-scritta"]) { const e = document.createElement("div"); e.id = id; document.body.appendChild(e); }
});
// Senza nessun tocco il gioco va in pausa da solo dopo un minuto: un finto movimento ogni tanto lo tiene sveglio.
await p.evaluate(() => setInterval(() => dispatchEvent(new PointerEvent("pointermove")), 10000));
// Le battute d'ingresso in piazza si saltano e la voce si ferma: il trailer parte in silenzio.
await p.evaluate(async () => { const { settle } = await import('/tests/helpers.mjs'); settle(game); window.voices?.stop?.(); });
// Il puntatore vero lontano, nell'angolo.
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 nella partita completa: 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) => p.evaluate((on) => { document.getElementById("tr-nero").style.opacity = on ? 1 : 0; }, on);
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]);
// In ogni scena: niente primo piano, niente battute in coda, flag di partenza; Andrea dove serve (o fuori campo).
const vai = (scene, at, flags = {}) => `async () => {
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, ${JSON.stringify(flags)});
game.go('${scene}'${at ? `, ${JSON.stringify(at)}` : ""}); settle(game);
${at ? "" : "game.state.x = -400;"}
}`;
const prepara = (codice) => p.evaluate(`(${codice})()`);
const fuoco = async (codice, veloce) => {
await p.evaluate((v) => { document.getElementById("tr-nero").style.transition = `opacity ${v ? .18 : .7}s`; }, veloce);
await nero(true); await sleep(veloce ? 200 : 750);
await prepara(codice);
await sleep(veloce ? 150 : 300);
await nero(false); await sleep(veloce ? 200 : 700);
};
const dice = (who, testo, pose) => p.evaluate(([w, t, po]) => game.run(w ? { say: t, who: w, pose: po } : { say: t }), [who, testo, pose]);
const finitoDiDire = () => p.waitForFunction(() => !game.speech, null, { timeout: 20000 });
// 1. Il nero, poi il panorama di Monte Cacioli con le nuvole.
await musica("assets/audio/sera.mp3");
await d.cue("apertura");
await sleep(800);
await p.evaluate(() => { document.getElementById("tr-scritta").style.whiteSpace = "pre-line"; });
await scritta("San Piero a Sieve\nmaggio 2007");
await sleep(2400);
await scritta(null);
await fuoco(vai("via_cafaggio"));
await sleep(3200);
// 2. La Fortezza al tramonto, le impronte.
await d.cue("notte");
await fuoco(vai("fortezza", null, { indizio_regolo: true }));
await scritta("La notte prima del Palio…", true);
await sleep(3000);
await scritta(null);
// 3. La scuola: il palio non c'è più.
await d.cue("sparito");
await fuoco(vai("scuola", [300, 330], { indizio_regolo: false }));
await scritta("…il palio è sparito.", true);
await sleep(3200);
await scritta(null);
// 4. Il giro completo del paese: Andrea cammina di scena in scena, con la musica del paese.
await d.cue("giro");
await musica("assets/audio/paese.mp3");
for (const [scene, da, a] of [["piazza", [120, 330], [300, 300]], ["via_de_medici", [300, 348], [300, 230]], ["via_compagnia", [300, 350], [430, 262]],
["villa_adami", [300, 345], [560, 272]], ["fortezza", [175, 352], [400, 300]], ["pieve", [40, 330], [260, 300]], ["via_cafaggio", [150, 352], [420, 250]],
["rotonda_cafaggio", [390, 352], [540, 230]], ["passeggiata_carza", [330, 352], [420, 250]], ["ponte_stazione", [300, 352], [200, 260]],
["orti_zio_mario", [400, 352], [330, 260]], ["stazione", [320, 350], [450, 290]], ["cardetole", [300, 350], [470, 300]], ["terrazza", [300, 330], [420, 320]]]) {
await fuoco(vai(scene, da), true);
await p.evaluate((a) => game.walkTo(a), a);
await sleep(3300);
}
// 5. Le sfide, giocate davvero per qualche secondo: ogni gioco col suo nome in basso e la sua musica.
await d.cue("sfide");
await musica(null);
await fuoco(`async () => { game.state.x = -400; }`);
await scritta("Un paese pieno di sfide");
await sleep(2000);
await scritta(null);
// Apre il primo piano, poi ogni 150 ms chiama `mossa` (il giocatore che gioca da solo) finché passano i secondi.
const gioca = async (cue, nome, brano, closeup, level, modulo, prima, mossa, secondi) => {
await d.cue(cue);
await musica(brano);
await fuoco(`async () => {
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;
game.run({ closeup: '${closeup}', level: '${level}' }); settle(game);
window.__m = await import('${modulo}');
const M = window.__m, c = game.closeup; ${prima}
}`);
await scritta(nome, true);
const fine = Date.now() + secondi * 1000;
while (Date.now() < fine) {
await p.evaluate(`(() => { const M = window.__m, c = game.closeup; if (!c) return; ${mossa} })()`);
if (Date.now() > fine - (secondi - 3) * 1000) await scritta(null);
await sleep(150);
}
};
await gioca("bocce", "Le bocce al pallaio", "assets/audio/bocce.mp3", "bocce", "seria", "/src/engine/bocce.js", "",
"if (M.canThrow(c)) { c.aim = { u: c.jack.u, w: c.jack.w }; c.power = M.powerFor(Math.hypot(c.jack.u, c.jack.w) - 1.5); M.shoot(c); }", 14);
await gioca("biliardo", "Il biliardo del Circolo", "assets/audio/biliardo.mp3", "biliardo", "seria", "/src/engine/biliardo.js", "",
"if (c.step === 'aim' && c.current === 'player' && !c.holding) M.strike(c, 'player', Math.atan2(c.balls.cpu.y - c.balls.player.y, c.balls.cpu.x - c.balls.player.x) + 0.04, 0.75);", 14);
await gioca("gara", "Il GP di Fortezza", "assets/audio/gara.mp3", "gara", "bona", "/src/engine/gara.js",
"while (c.step === 'intro') M.press(c); M.release(c);", "c.holding = c.step === 'race' && !M.mustLift(c);", 16);
await gioca("carte", "La briscola all'oratorio", "assets/audio/carte.mp3", "briscola", "seria", "/src/engine/briscola.js", "",
"if (M.canPlay(c)) M.play(c, 0);", 12);
await gioca("macchinine", "Le macchinine col gesso", "assets/audio/macchinine.mp3", "micro", "bona", "/src/engine/micro.js", "M.pick(c, 0);",
"if (M.canThrow(c)) { const car = c.cars.player, i = (car.idx + Math.round(110 / c.track.step)) % c.track.n, [x, y] = c.track.pts[i]; M.strike(c, 'player', Math.atan2(y - car.y, x - car.x), M.powerFor(Math.hypot(x - car.x, y - car.y))); }", 12);
await gioca("cerbottana", "La cerbottana", "assets/audio/gara.mp3", "cerbottana", "seria", "/src/engine/cerbottana.js", "",
"if (M.canThrow(c)) { const k = c.cans.find(k => !k.down); c.aim = k && c.left % 2 ? { x: k.x, y: M.LEDGE - 8 } : { x: 320 + (c.left % 3 - 1) * 6, y: 118 }; c.power = M.BEST; M.shoot(c); }", 10);
await musica("assets/audio/paese.mp3");
// 6. Le vecchie conoscenze, con le loro battute.
await d.cue("amici");
await fuoco(vai("via_liguri_magelli", [330, 300]));
await scritta("…e di vecchie conoscenze", true);
await dice(null, "Di chi è quell'Audi lì?");
await finitoDiDire();
await scritta(null);
await dice("arcu", "Let it be.", "explain");
await finitoDiDire();
await sleep(300);
await dice(null, "Zio, hai problemi?");
await finitoDiDire();
await dice("zio_mario", "I problemi io gli avevo quando andavo a scuola!", "laugh");
await finitoDiDire();
await fuoco(vai("cardetole", [470, 330]), true);
await dice("chiaru", "Vorrai dire un giochino!", "laugh");
await finitoDiDire();
await sleep(400);
// 7. Il buio: la sera, la torcia nella fessura, gli occhi gialli.
await d.cue("buio");
// Sotto il buio una musica bassa di suspense (Andrea: «un po' di musichetta nel finale»).
await musica("assets/audio/sera.mp3");
await fuoco(vai("fortezza", [462, 310], { indizio_regolo: true, casetta_aperta: true }));
await sleep(900);
await p.evaluate(() => { game.state.inventory.push("torcia"); game.act("use", "fessura", "torcia"); });
// «Accendo la torcia e la punto dentro…» si salta: il primo piano si apre subito.
// Il palio non si vede (Andrea: si capirebbe che è in Fortezza): la luce parte da destra, nel buio, e i primi due
// ritrovamenti si danno per fatti; si cercano solo gli occhi.
for (let k = 0; k < 60 && await p.evaluate(() => game.closeup?.id !== "fessura"); k++) { await p.evaluate(() => game.skip()); await sleep(50); }
await p.evaluate(() => { const c = game.closeup; c.found = 2; c.beam = [600, 300]; });
await sleep(1600);
const targets = await p.evaluate(() => game.story.closeups.fessura.targets.map((t) => t.at));
for (let i = 2; i < targets.length; i++) {
if (i === 2) await musica("assets/audio/cartello.mp3");
// La luce va piano verso il punto, come una mano che cerca: prima in basso, poi su verso gli occhi.
const from = await p.evaluate(() => game.closeup.beam);
for (let k = 1; k <= 30; k++) {
const x = from[0] + (targets[i][0] - from[0]) * k / 30 + Math.sin(k / 4) * 12, y = from[1] + (targets[i][1] - from[1]) * k / 30;
await p.evaluate(([x, y]) => game.closeupAim([x, y]), [x, y]);
await sleep(50);
}
// Le battute dei primi ritrovamenti si saltano (partono prima che il ritrovamento conti, e lo fermano); resta solo
// l'ultima: «…sono due occhi gialli!».
for (let k = 0; k < 300 && await p.evaluate((i) => !(game.closeup?.found > i), i); k++) {
if (i < 2) await p.evaluate(() => game.skip());
await sleep(100);
}
await p.waitForFunction(() => !game.speech && !game.closeup?.lines?.length, null, { timeout: 30000 });
await sleep(i === 2 ? 1400 : 100);
}
await nero(true);
await sleep(1400);
// 8. Il titolo.
await d.cue("titolo");
await musica("assets/audio/titolo.mp3");
await p.evaluate(() => {
game.closeup = null;
document.getElementById("title").hidden = false;
document.getElementById("tr-nero").style.transition = "opacity 1.6s";
});
await sleep(300);
await nero(false);
await sleep(7500);
await nero(true);
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("trailer-extended-audio.json", out), JSON.stringify({ eventi: audio }, null, 2));
}
+238
View File
@@ -0,0 +1,238 @@
// Copione del trailer «La Leggenda del Regolo» (scaletta approvata da Andrea il 29 ottobre): circa 75 secondi, la scena a
// tutto schermo, scritte grandi e stacchi al nero fatti dentro la pagina, niente puntatore e niente clic.
// Si gira come la partita completa (video/SCALETTA.md), poi: python3 video/monta.py <cartella> --trailer .
// La musica non è quella che sceglierebbe il gioco: il copione annota i brani voluti, e le voci che partono, in
// out/trailer-audio.json; monta.py rimonta la colonna sonora.
import { mkdirSync, writeFileSync } from "node:fs";
const c = (id, it) => ({ id, it, en: it, top: true });
export const title = { it: "La Leggenda del Regolo · trailer", en: "La Leggenda del Regolo · trailer" };
export const cues = [
c("apertura", "San Piero a Sieve, maggio 2007"),
c("notte", "La notte prima del Palio…"),
c("sparito", "…il palio è sparito."),
c("paese", "il paese"),
c("sfide", "Un paese pieno di sfide"),
c("amici", "…e di vecchie conoscenze"),
c("buio", "la fessura"),
c("titolo", "La Leggenda del Regolo"),
];
const URL_GIOCO = "http://127.0.0.1:8000/";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
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])");
// Si entra in partita dietro le quinte: rione, introduzione saltata.
await p.click("#new-game");
await p.click('#rione [data-rione="campomigliaio"]');
await p.click("#skip-intro");
// Il palcoscenico del trailer: solo la scena, a tutto schermo; sopra, il nero e le scritte.
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; }
#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: 42px; line-height: 1.6; color: #ffe9b0; opacity: 0;
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: 32px; }
` });
await p.evaluate(() => {
for (const id of ["tr-nero", "tr-scritta"]) { const e = document.createElement("div"); e.id = id; document.body.appendChild(e); }
});
// Senza nessun tocco il gioco va in pausa da solo dopo un minuto: un finto movimento ogni tanto lo tiene sveglio.
await p.evaluate(() => setInterval(() => dispatchEvent(new PointerEvent("pointermove")), 10000));
// Le battute d'ingresso in piazza si saltano e la voce si ferma: il trailer parte in silenzio.
await p.evaluate(async () => { const { settle } = await import('/tests/helpers.mjs'); settle(game); window.voices?.stop?.(); });
// Il puntatore vero lontano, nell'angolo.
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 nella partita completa: 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) => p.evaluate((on) => { document.getElementById("tr-nero").style.opacity = on ? 1 : 0; }, on);
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]);
// In ogni scena: niente primo piano, niente battute in coda, flag di partenza; Andrea dove serve (o fuori campo).
const vai = (scene, at, flags = {}) => `async () => {
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, ${JSON.stringify(flags)});
game.go('${scene}'${at ? `, ${JSON.stringify(at)}` : ""}); settle(game);
${at ? "" : "game.state.x = -400;"}
}`;
const prepara = (codice) => p.evaluate(`(${codice})()`);
const fuoco = async (codice, veloce) => {
await p.evaluate((v) => { document.getElementById("tr-nero").style.transition = `opacity ${v ? .18 : .7}s`; }, veloce);
await nero(true); await sleep(veloce ? 200 : 750);
await prepara(codice);
await sleep(veloce ? 150 : 300);
await nero(false); await sleep(veloce ? 200 : 700);
};
const dice = (who, testo, pose) => p.evaluate(([w, t, po]) => game.run(w ? { say: t, who: w, pose: po } : { say: t }), [who, testo, pose]);
const finitoDiDire = () => p.waitForFunction(() => !game.speech, null, { timeout: 20000 });
// 1. Il nero, poi il panorama di Monte Cacioli con le nuvole.
await musica("assets/audio/sera.mp3");
await d.cue("apertura");
await sleep(800);
await p.evaluate(() => { document.getElementById("tr-scritta").style.whiteSpace = "pre-line"; });
await scritta("San Piero a Sieve\nmaggio 2007");
await sleep(2400);
await scritta(null);
await fuoco(vai("via_cafaggio"));
await sleep(3200);
// 2. La Fortezza al tramonto, le impronte.
await d.cue("notte");
await fuoco(vai("fortezza", null, { indizio_regolo: true }));
await scritta("La notte prima del Palio…", true);
await sleep(3000);
await scritta(null);
// 3. La scuola: il palio non c'è più.
await d.cue("sparito");
await fuoco(vai("scuola", [300, 330], { indizio_regolo: false }));
await scritta("…il palio è sparito.", true);
await sleep(3200);
await scritta(null);
// 4. Il paese, a stacchi: Andrea cammina.
await d.cue("paese");
await musica("assets/audio/gara.mp3");
for (const [scene, da, a] of [["piazza", [120, 330], [300, 300]], ["pieve", [40, 330], [260, 300]], ["passeggiata_carza", [330, 352], [420, 250]],
["terrazza", [300, 330], [420, 320]], ["ponte_stazione", [300, 352], [200, 260]]]) {
await fuoco(vai(scene, da), true);
await p.evaluate((a) => game.walkTo(a), a);
await sleep(2100);
}
// 5. Le sfide: un lampo per minigioco.
await d.cue("sfide");
await scritta("Un paese pieno di sfide");
await sleep(1600);
await scritta(null);
const sfida = async (closeup, level, dopo = "") => {
await fuoco(`async () => {
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;
game.run({ closeup: '${closeup}', level: '${level}' }); settle(game);
${dopo}
}`, true);
await sleep(1300);
};
await sfida("gara", "seria", "const g = await import('/src/engine/gara.js'); while (game.closeup.step === 'intro') g.press(game.closeup);");
await sfida("biliardo", "seria", "const b = await import('/src/engine/biliardo.js'); const c = game.closeup; b.strike(c, 'player', Math.atan2(c.balls.cpu.y - c.balls.player.y, c.balls.cpu.x - c.balls.player.x), 0.8);");
await sfida("rinquarto", "allenamento");
await sfida("micro", "seria", "const m = await import('/src/engine/micro.js'); const c = game.closeup; m.pick(c, 0); m.strike(c, 'player', c.angle, 0.55);");
await sfida("cerbottana", "seria", "const m = await import('/src/engine/cerbottana.js'); const c = game.closeup; c.aim = { x: 320, y: 118 }; c.power = m.BEST; m.shoot(c);");
await sfida("briscola", "seria");
await sfida("basket", "seria", "const b = await import('/src/engine/basket.js'); const c = game.closeup; c.power = 0.62; b.shoot(c);");
// 6. Le vecchie conoscenze, con le loro battute.
await d.cue("amici");
await fuoco(vai("via_liguri_magelli", [330, 300]));
await scritta("…e di vecchie conoscenze", true);
await dice(null, "Di chi è quell'Audi lì?");
await finitoDiDire();
await scritta(null);
await dice("arcu", "Let it be.", "explain");
await finitoDiDire();
await sleep(300);
await dice(null, "Zio, hai problemi?");
await finitoDiDire();
await dice("zio_mario", "I problemi io gli avevo quando andavo a scuola!", "laugh");
await finitoDiDire();
await fuoco(vai("cardetole", [470, 330]), true);
await dice("chiaru", "Vorrai dire un giochino!", "laugh");
await finitoDiDire();
await sleep(400);
// 7. Il buio: la sera, la torcia nella fessura, gli occhi gialli.
await d.cue("buio");
// Sotto il buio una musica bassa di suspense (Andrea: «un po' di musichetta nel finale»).
await musica("assets/audio/sera.mp3");
await fuoco(vai("fortezza", [462, 310], { indizio_regolo: true, casetta_aperta: true }));
await sleep(900);
await p.evaluate(() => { game.state.inventory.push("torcia"); game.act("use", "fessura", "torcia"); });
// «Accendo la torcia e la punto dentro…» si salta: il primo piano si apre subito.
// Il palio non si vede (Andrea: si capirebbe che è in Fortezza): la luce parte da destra, nel buio, e i primi due
// ritrovamenti si danno per fatti; si cercano solo gli occhi.
for (let k = 0; k < 60 && await p.evaluate(() => game.closeup?.id !== "fessura"); k++) { await p.evaluate(() => game.skip()); await sleep(50); }
await p.evaluate(() => { const c = game.closeup; c.found = 2; c.beam = [600, 300]; });
await sleep(1600);
const targets = await p.evaluate(() => game.story.closeups.fessura.targets.map((t) => t.at));
for (let i = 2; i < targets.length; i++) {
if (i === 2) await musica("assets/audio/cartello.mp3");
// La luce va piano verso il punto, come una mano che cerca: prima in basso, poi su verso gli occhi.
const from = await p.evaluate(() => game.closeup.beam);
for (let k = 1; k <= 30; k++) {
const x = from[0] + (targets[i][0] - from[0]) * k / 30 + Math.sin(k / 4) * 12, y = from[1] + (targets[i][1] - from[1]) * k / 30;
await p.evaluate(([x, y]) => game.closeupAim([x, y]), [x, y]);
await sleep(50);
}
// Le battute dei primi ritrovamenti si saltano (partono prima che il ritrovamento conti, e lo fermano); resta solo
// l'ultima: «…sono due occhi gialli!».
for (let k = 0; k < 300 && await p.evaluate((i) => !(game.closeup?.found > i), i); k++) {
if (i < 2) await p.evaluate(() => game.skip());
await sleep(100);
}
await p.waitForFunction(() => !game.speech && !game.closeup?.lines?.length, null, { timeout: 30000 });
await sleep(i === 2 ? 1400 : 100);
}
await nero(true);
await sleep(1400);
// 8. Il titolo.
await d.cue("titolo");
await musica("assets/audio/titolo.mp3");
await p.evaluate(() => {
game.closeup = null;
document.getElementById("title").hidden = false;
document.getElementById("tr-nero").style.transition = "opacity 1.6s";
});
await sleep(300);
await nero(false);
await sleep(7500);
await nero(true);
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("trailer-audio.json", out), JSON.stringify({ eventi: audio }, null, 2));
}