Forno, stazione e chiesa; uscite della rotonda e di Via Calimara come il paese vero; mappa ordinata

- Dalla Pieve a destra il forno e, a sinistra, la stazione; dal portone centrale si entra nella Pieve
  (foto di Mariano Mirannalti, nei crediti alla voce «Fotografie»).
- Via Calimara: in fondo la Piazza, verso chi guarda la farmacia. Rotonda del River: a sinistra il campo
  sportivo, in basso l'area feste, dietro la farmacia la Pieve, a destra il Comune, in basso a destra Calimara.
- Polisportiva: dal ponte prima il campo principale, poi il secondario col padel; dietro, l'area feste.
- Andrea ha 27 anni nel 2007: battute, documento e CLAUDE.md corretti.
- Mappa su una griglia ordinata, senza etichette sovrapposte.
- Doppio clic: Andrea va al doppio della velocità. In pausa la musica sfuma e si ferma; dopo un minuto
  senza toccare niente il gioco va in pausa da solo.
- Crediti: scorre solo l'elenco, dedica e Chiudi sempre in vista.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017egeN2amL84ErjfgQFtEDn
This commit is contained in:
2026-10-23 20:00:28 +02:00
co-authored by Claude Opus 5.5
parent 043a5abdcb
commit 2d682d8ae9
53 changed files with 341 additions and 93 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
# La Leggenda del Regolo — note per Claude
Avventura grafica punta-e-clicca nello spirito delle avventure LucasArts, ambientata a San Piero a Sieve (Mugello)
nel 2007, durante il Palio della Fortezza. Protagonista: Andrea (l'utente) da ragazzo. Tono comico con nostalgia.
nel 2007, durante il Palio della Fortezza. Protagonista: Andrea (l'utente), che nel 2007 ha 27 anni (è nato nel 1980). Tono comico con nostalgia.
**Il gioco è dedicato al Lele**, l'amico di Andrea che gli fece conoscere Monkey Island e che è scomparso troppo
presto. Trattare questo tema con delicatezza; come e quanto farlo comparire nel gioco lo decide Andrea.
Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 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.
+102 -30
View File
@@ -219,6 +219,34 @@
"file": "andrea-d7882fce87.mp3",
"sec": 3.35
},
"andrea|L'altare, coi candelieri dorati e i fiori freschi. Qualcuno li cambia ogni mattina, prima di tutti.": {
"file": "andrea-a79f677fc1.mp3",
"sec": 8.38
},
"andrea|Un candeliere? Mi sa che San Pietro le chiavi del Paradiso me le toglie per sempre.": {
"file": "andrea-5df5f2d69e.mp3",
"sec": 6.94
},
"andrea|Il grande crocifisso di legno, davanti alla statua di San Pietro.": {
"file": "andrea-055b06b557.mp3",
"sec": 4.22
},
"andrea|Qui si abbassa la voce senza che nessuno lo chieda.": {
"file": "andrea-62fb1fdbf2.mp3",
"sec": 3.36
},
"andrea|San Pietro, il patrono. Quello fuori sul sagrato parla. Questo, per fortuna, no.": {
"file": "andrea-51f5bdb47f.mp3",
"sec": 8.54
},
"andrea|Le panche dove ci siamo seduti tutti, dal battesimo in poi. Le più scomode del Mugello.": {
"file": "andrea-b915fb9eba.mp3",
"sec": 6.28
},
"andrea|Un minuto seduto... no, se mi siedo qui mi addormento come alla messa delle undici.": {
"file": "andrea-d309ba9dfc.mp3",
"sec": 5.72
},
"andrea|Il palco dell'oratorio. D'estate ci fanno le recite, la tombola e i concerti.": {
"file": "andrea-058d10ea48.mp3",
"sec": 7.18
@@ -327,9 +355,9 @@
"file": "andrea-69dce8d64f.mp3",
"sec": 3.6
},
"andrea|Lo scivolo e le altalene. Sono troppo grande, ufficialmente. Ufficiosamente, un giro ci starebbe.": {
"file": "andrea-4a7bfec8b2.mp3",
"sec": 7.99
"andrea|Lo scivolo e le altalene. Ho ventisette anni, ufficialmente. Ufficiosamente, un giro ci starebbe.": {
"file": "andrea-611215643c.mp3",
"sec": 7.56
},
"andrea|Un giro sull'altalena! ... No, c'è troppa gente che mi conosce.": {
"file": "andrea-40c93c1f52.mp3",
@@ -399,17 +427,25 @@
"file": "andrea-ea4d85e1ca.mp3",
"sec": 5.15
},
"andrea|Più avanti ci sono i campi da tennis e da padel.": {
"file": "andrea-54884b5fa9.mp3",
"sec": 3.54
"andrea|Più avanti ci sono i campi da tennis. Racchette, palline e gente che si lamenta del sole.": {
"file": "andrea-69eaa84de0.mp3",
"sec": 7.13
},
"andrea|Il padel, nel 2007, non sapevo nemmeno cosa fosse. Lo scopriranno i miei genitori, tra qualche anno.": {
"file": "andrea-c30bff01c2.mp3",
"sec": 7.54
"andrea|Laggiù i campi da tennis.": {
"file": "andrea-11b12d551f.mp3",
"sec": 2.18
},
"andrea|Laggiù i campi da tennis e da padel. Racchette, palline e gente che si lamenta del sole.": {
"file": "andrea-daced50ca3.mp3",
"sec": 7.5
"andrea|Il capannone verde del padel.": {
"file": "andrea-a16136da3a.mp3",
"sec": 1.63
},
"andrea|Il padel, nel 2007, non so nemmeno cosa sia. Tra qualche anno ci giocheranno tutti, me compreso.": {
"file": "andrea-1baedec826.mp3",
"sec": 7.77
},
"andrea|Chiuso. E poi non saprei nemmeno da che parte si tiene la racchetta.": {
"file": "andrea-e47b3cebb5.mp3",
"sec": 4.87
},
"andrea|La tribuna bianca e rossa. La domenica qui c'è più tifo che al Palio. Quasi.": {
"file": "andrea-a50071085e.mp3",
@@ -423,6 +459,42 @@
"file": "andrea-40b7bf59ae.mp3",
"sec": 4.99
},
"andrea|Il forno. Il profumo del pane arriva fino alla Pieve, e la domenica mattina fino a casa mia.": {
"file": "andrea-436034d37e.mp3",
"sec": 6.62
},
"andrea|Una schiacciata calda... no, prima il palio. Però l'odore me lo porto dietro.": {
"file": "andrea-ac2407893f.mp3",
"sec": 6.62
},
"andrea|Più avanti c'è «alla Luna», e poi Campomigliaio, il mio rione: i blu.": {
"file": "andrea-f66b1e4179.mp3",
"sec": 5.22
},
"andrea|Ci torno quando ho ritrovato il palio. A mani vuote non mi ci faccio vedere.": {
"file": "andrea-df05ecea32.mp3",
"sec": 4.54
},
"andrea|La Provinciale va avanti verso «alla Luna» e Campomigliaio.": {
"file": "andrea-a964de1d7b.mp3",
"sec": 2.95
},
"andrea|La stazione della Faentina. Da qui i treni vanno a Firenze e a Faenza, con calma.": {
"file": "andrea-647ffbf576.mp3",
"sec": 6.3
},
"andrea|Molta calma.": {
"file": "andrea-47b4e5ab68.mp3",
"sec": 1.04
},
"andrea|Il bar della stazione. Qui si aspetta il treno, e quando il treno è in ritardo si aspetta un altro caffè.": {
"file": "andrea-5d014ec41b.mp3",
"sec": 6.97
},
"andrea|Non ho tempo per un caffè. E poi il caffè mi fa tremare come la carretta del Palio.": {
"file": "andrea-b00aebf83a.mp3",
"sec": 6.31
},
"andrea|Villa Adami. Gialla come un tuorlo, con le persiane verdi.": {
"file": "andrea-225e29ed43.mp3",
"sec": 5.12
@@ -455,9 +527,9 @@
"file": "andrea-468fe91000.mp3",
"sec": 3.28
},
"andrea|Di là si va alla scuola. Ci passo già abbastanza tempo durante l'anno.": {
"file": "andrea-b3acca1e65.mp3",
"sec": 5.34
"andrea|Di là si va alla scuola. Ci ho passato abbastanza anni, grazie.": {
"file": "andrea-f6fa5d6d6d.mp3",
"sec": 4.03
},
"andrea|Il muro vecchio di sassi, mezzo coperto dai rampicanti. Qui ci si nascondeva a nascondino.": {
"file": "andrea-ecc1aee547.mp3",
@@ -519,29 +591,29 @@
"file": "andrea-ea832da7b0.mp3",
"sec": 4.01
},
"andrea|Arrampicarmi? Mia mamma mi vede dalla finestra.": {
"file": "andrea-c73c366e50.mp3",
"sec": 3.99
"andrea|Arrampicarmi? Alla mia età, e davanti a tutto il condominio?": {
"file": "andrea-018183c4dd.mp3",
"sec": 5.82
},
"andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto.": {
"file": "andrea-0eea4f27c6.mp3",
"sec": 5.3
"andrea|Un pino a ombrello enorme. Alla ricreazione, tutti all'ombra lì sotto. Sembra ieri.": {
"file": "andrea-0a073729fd.mp3",
"sec": 7.3
},
"andrea|Il tricolore e la bandiera europea. Almeno loro a scuola ci stanno volentieri.": {
"file": "andrea-929dc868d7.mp3",
"sec": 5.91
},
"andrea|Rubare una bandiera davanti alla scuola? Poi chi la sente la preside.": {
"file": "andrea-7655ebd550.mp3",
"sec": 5.53
"andrea|Rubare una bandiera davanti alla scuola? Ci manca solo che mi richiamino in presidenza, a ventisette anni.": {
"file": "andrea-bed4ce97bd.mp3",
"sec": 6.99
},
"andrea|Le porte a vetri della scuola. Di là ci sono i banchi, la lavagna e la campanella.": {
"file": "andrea-a24a09d869.mp3",
"sec": 7.5
},
"andrea|A scuola? A maggio? Nemmeno per sogno.": {
"file": "andrea-72e4ebb412.mp3",
"sec": 3.88
"andrea|Tornare a scuola? A ventisette anni? Nemmeno per sogno.": {
"file": "andrea-8ae8691b63.mp3",
"sec": 5.44
},
"andrea|Le palazzine gialle accanto alla scuola, con la scala arancione.": {
"file": "andrea-528eda3257.mp3",
@@ -579,9 +651,9 @@
"file": "andrea-658f69bfec.mp3",
"sec": 4.35
},
"andrea|Una vite? Ci vuole la vendemmia, non un ragazzo.": {
"file": "andrea-fe5b32c079.mp3",
"sec": 4.13
"andrea|Una vite? Ci vuole la vendemmia, non uno con le mani in tasca.": {
"file": "andrea-70ecfbf3f3.mp3",
"sec": 4.85
},
"andrea|La scala che sale al pergolato.": {
"file": "andrea-e8fba1f859.mp3",
+1
View File
@@ -24,6 +24,7 @@ ne è un'opera derivata. Le foto scattate da Andrea non hanno problemi; quelle p
| `campeggio.png` | Codex | `campeggio-ingresso.png` | [DA ANDREA] probabilmente dal sito del campeggio | ? |
| `polisportiva-*.png`, `ponte-sieve.png`, `area-feste.png`, `via-provinciale.png`, `comune-asilo.png`, `giardini-poste.png`, `stazione.png` | Codex | foto omonime in `assets/raw/foto/` | Andrea (EXIF, 23 ottobre 2026) | sì |
| `cardetole.png` | Codex | `cardetole.jpg` | Andrea (EXIF) | **da decidere**: è la casa vera di Andrea, pubblicarla dice dove abita |
| `pieve-interno.png` | Codex | `pieve-interno.png` | Mariano Mirannalti (mandata ad Andrea su WhatsApp) | sì, se Mariano è d'accordo; citarlo nei crediti |
| `panorama-intro.png` | Codex | `altre/panorama-intro.png` | da internet (screenshot con l'icona di Google Lens) | **no**, finché non si sostituisce |
Per uno sfondo «da internet» le strade sono tre:
+3 -3
View File
@@ -4,7 +4,7 @@
Avventura grafica punta-e-clicca nello spirito delle avventure LucasArts dei primi anni '90, ambientata a
**San Piero a Sieve** (Mugello) durante la settimana del **Palio della Fortezza** del **2007**.
Tono **comico con nostalgia**. Protagonista: **Andrea da ragazzo**.
Tono **comico con nostalgia**. Protagonista: **Andrea a 27 anni** (è nato nel 1980; nel 2007 ne ha 27).
Questo documento è la prima bozza: tutto quello che è segnato **[DA ANDREA]** aspetta ricordi veri.
@@ -119,7 +119,7 @@ Livello 0 Piazza Colonna ─────────────────
Per Andrea il Trebbio è il posto dove salire a cercare pace (scherzando: il suo psicologo). Nel gioco diventa
il **sistema di aiuti**:
- quando sei bloccato, **sali al Trebbio**: il ragazzo si siede sotto il pergolato, guarda il Mugello dall'alto,
- quando sei bloccato, **sali al Trebbio**: Andrea si siede sotto il pergolato, guarda il Mugello dall'alto,
la musica si calma, e nella pace gli viene l'idea giusta (un indizio graduato: prima vago, poi più preciso);
- il lato comico: la "seduta" è un dialogo con il castello stesso, che ascolta paziente e risponde con calma toscana;
- **contrasto voluto**: il Trebbio è la luce e la pace, la Fortezza il buio e la leggenda. Il gioco vive tra questi
@@ -141,7 +141,7 @@ Storia vera da usare per battute e segreti:
| Personaggio | Dove | Idea | Stato |
| --- | --- | --- | --- |
| **Andrea** (ragazzo) | ovunque | il protagonista: curioso, pigro quando serve, testardo quando conta | aspetto dal riferimento di Andrea: capelli castani ricci, maglietta blu con il triangolo azzurro, jeans chiari, scarpe da ginnastica bianche; battute [DA ANDREA] |
| **Andrea** (27 anni nel 2007) | ovunque | il protagonista: curioso, pigro quando serve, testardo quando conta | aspetto dal riferimento di Andrea: capelli castani ricci, maglietta blu con il triangolo azzurro, jeans chiari, scarpe da ginnastica bianche; battute [DA ANDREA] |
| L'Arcu (Marco Arcuri) | ovunque | il migliore amico di Andrea, compagno di rione nei blu: la spalla dell'avventura? | aspetto dal riferimento: capelli lunghi raccolti, maglietta blu, guanti bianchi, jeans scuri; persona vera: [DA ANDREA: consenso di Marco] |
| Il barista | Piazza | sa tutto di tutti, non dice niente gratis | [DA ANDREA: chi c'era nel 2007?] |
| I vecchietti della panchina | Piazza | il "coro" del paese: commentano tutto, litigano sulla leggenda | [DA ANDREA] |
+3 -2
View File
@@ -26,9 +26,10 @@ export class Command {
return isItem ? `Usa ${name} con…` : `${VERB_LABELS[g.defaultVerb(target)]} ${name}`;
}
// Clic sulla scena: `look` per il clic destro o la pressione lunga.
clickScene(point, { look = false } = {}) {
// Clic sulla scena: `look` per il clic destro o la pressione lunga, `hurry` per il doppio clic (passo doppio).
clickScene(point, { look = false, hurry = false } = {}) {
const g = this.game;
g.hurry = hurry;
if (g.skip()) return 'skip';
if (g.dialogue || g.busy) return null;
const h = g.hotspotAt(point);
+2 -1
View File
@@ -62,6 +62,7 @@ export class Game {
this.walking = false;
this.time = 0;
this.stride = 0; // strada fatta, per scegliere il passo della camminata
this.hurry = false; // doppio clic: si cammina al doppio della velocità
this.reach = 0; // secondi di braccio teso quando prende qualcosa
this.back = false; // di spalle davanti a una porta o a una bacheca
}
@@ -213,7 +214,7 @@ export class Game {
}
if (this.walking) {
const scale = this.scaleAt(this.state.y);
let step = SPEED * scale * dt;
let step = SPEED * (this.hurry ? 2 : 1) * scale * dt;
this.stride += step / scale;
while (step > 0 && this.path.length) {
const target = this.path[0], d = dist([this.state.x, this.state.y], target);
+4 -1
View File
@@ -23,6 +23,7 @@ export class Music {
this.current = null; // { src, audio }
this.old = [];
this.duck = 1; // 1 = pieno; meno di 1 mentre parla qualcuno
this.hold = false; // in pausa: il brano sfuma e si ferma, poi riprende da dov'era
}
// Chiamata a ogni fotogramma con il brano voluto: avvia, cambia e sfuma.
@@ -33,10 +34,12 @@ export class Music {
this.current = src ? { src, audio: this.start(src) } : null;
}
// Il brano in corso sale (o scende) verso il volume scelto; quelli vecchi sfumano fino a zero.
const step = dt / FADE, target = this.muted ? 0 : this.volume * this.duck;
const step = dt / FADE, target = this.muted || this.hold ? 0 : this.volume * this.duck;
if (this.current) {
const a = this.current.audio;
a.volume = a.volume < target ? Math.min(target, a.volume + step) : Math.max(target, a.volume - step);
if (this.hold && a.volume === 0 && !a.paused) a.pause();
else if (!this.hold && a.paused && a !== this.blocked) a.play().catch(() => { this.blocked = a; });
}
for (const a of this.old) a.volume = Math.max(0, a.volume - step);
this.old = this.old.filter(a => a.volume > 0 || (a.pause(), false));
+18 -2
View File
@@ -10,7 +10,10 @@ import { Voices } from './engine/voices.js';
const $ = id => document.getElementById(id);
const canvas = $('scene'), ctx = canvas.getContext('2d');
const storage = (() => { try { return window.localStorage; } catch { return null; } })();
const LONG_PRESS = 450;
const LONG_PRESS = 450, DOUBLE_CLICK = 350, IDLE_PAUSE = 60000;
let idleSince = performance.now();
for (const ev of ['pointerdown', 'pointermove', 'keydown', 'wheel']) addEventListener(ev, () => { idleSince = performance.now(); }, { capture: true, passive: true });
let lastTap = null;
const debug = new URLSearchParams(location.search).has('debug');
let game = null, command = null, hoverTarget = null, mouse = null, running = false;
@@ -106,12 +109,16 @@ $('map-button').addEventListener('click', openMap);
function openPause() {
if (!running) return;
running = false;
music.hold = true;
voices.stop();
command.reset();
$('pause-map').hidden = game.mapPlaces().length < 2;
$('pause').hidden = false;
}
function closePause() {
$('pause').hidden = true;
music.hold = false;
idleSince = performance.now();
running = true;
}
$('pause-button').addEventListener('click', openPause);
@@ -119,6 +126,7 @@ $('resume').addEventListener('click', closePause);
$('pause-map').addEventListener('click', () => { closePause(); openMap(); });
$('to-title').addEventListener('click', () => {
$('pause').hidden = true;
music.hold = false;
game.onSave(game.snapshot());
showTitle();
});
@@ -145,7 +153,13 @@ canvas.addEventListener('pointerdown', e => {
canvas.addEventListener('pointerup', e => {
if (!press) return;
clearTimeout(press.timer);
if (!press.long) command.clickScene(scenePoint(e));
// Il secondo clic vicino al primo, entro poco, è un doppio clic: stesso comando, al doppio della velocità.
if (!press.long) {
const point = scenePoint(e), now = performance.now();
const hurry = !!lastTap && now - lastTap.time < DOUBLE_CLICK && Math.hypot(point[0] - lastTap.point[0], point[1] - lastTap.point[1]) < 24;
command.clickScene(point, { hurry });
lastTap = hurry ? null : { time: now, point };
}
press = null;
});
canvas.addEventListener('pointercancel', () => { if (press) clearTimeout(press.timer); press = null; });
@@ -281,6 +295,8 @@ function frame(now) {
const screen = !$('title').hidden ? 'title' : !$('splash').hidden ? 'splash' : !$('intro').hidden ? 'intro' : !$('ending').hidden ? 'ending' : 'game';
music.duck = voices.playing ? 0.45 : 1;
music.update(trackFor(story, { screen, scene: game?.state.scene }), dt);
// Un minuto senza toccare niente, a gioco fermo: pausa da sola.
if (running && now - idleSince > IDLE_PAUSE && !game.speech && !game.walking && !game.queue.length) openPause();
if (game) {
if (running) game.update(dt);
render(ctx, game, { hover: debug && hoverTarget ? game.hotspot(hoverTarget) : null });
+167 -49
View File
@@ -24,6 +24,8 @@ export const story = {
{ role: 'Storia e aneddoti di San Piero', names: ['Francesco Bacci'] },
{ role: 'Musica', names: ['Andrea Manzi, con Suno'] },
{ role: 'Sfondi', names: ['dalle fotografie di San Piero di Andrea'] },
// Chi ci ha mandato foto, ricordi o idee: una riga per contributo, si allunga man mano.
{ role: 'Fotografie', names: ['Mariano Mirannalti (l\'interno della Pieve)'] },
{ role: 'Voci', names: ['XTTS-v2 di Coqui'] },
{ role: 'Programmazione', names: ['Andrea Manzi, con Claude e Codex'] },
],
@@ -72,7 +74,7 @@ export const story = {
scenes: {
piazza: {
name: 'Piazza Colonna',
map: [16, 70],
map: [32, 45],
background: 'assets/scenes/piazza-colonna.png',
walk: [[84, 244], [330, 238], [345, 230], [362, 238], [462, 246], [466, 276], [544, 276], [548, 248], [588, 250], [600, 290], [640, 300], [640, 360], [0, 360], [0, 330], [84, 318]],
depth: [[236, 0.5], [360, 1.35]],
@@ -150,7 +152,7 @@ export const story = {
id: 'verso_comune', name: 'a sinistra, verso il Comune', box: [96, 196, 50, 54], at: [120, 252], exit: { scene: 'comune', at: [20, 300], face: 'right' },
},
{
id: 'verso_calimara', name: 'Via Calimara, verso la farmacia', box: [180, 334, 380, 26], at: [360, 354], exit: { scene: 'via_calimara', at: [300, 350], face: 'up' },
id: 'verso_calimara', name: 'Via Calimara, verso la farmacia', box: [180, 334, 380, 26], at: [360, 354], exit: { scene: 'via_calimara', at: [365, 212], face: 'down' },
},
{
id: 'verso_pieve', name: 'verso la Pieve', box: [612, 292, 28, 68], at: [628, 330], exit: { scene: 'pieve', at: [30, 320], face: 'right' },
@@ -160,7 +162,7 @@ export const story = {
},
fortezza: {
name: 'Sotto le mura della Fortezza',
map: [30, 15],
map: [36, 6],
background: 'assets/scenes/fortezza-esterno.png',
walk: [[110, 284], [300, 280], [330, 272], [348, 274], [400, 292], [442, 294], [640, 352], [640, 360], [0, 360], [0, 315], [60, 310], [105, 295]],
depth: [[272, 0.55], [360, 1.15]],
@@ -211,7 +213,7 @@ export const story = {
// Livello 0: la Pieve vista dalla salita, con la salita per Villa Adami alle spalle.
pieve: {
name: 'La Pieve',
map: [46, 70],
map: [56, 45],
background: 'assets/scenes/pieve-dalla-salita.png',
speakers: { san_pietro: [343, 166] },
// La rampa in primo piano e, dietro il muretto, il sagrato (ci si arriva da sinistra).
@@ -226,6 +228,9 @@ export const story = {
open: [{ say: 'Il prete è a benedire le carrette del Palio. Torno dopo.' }],
},
},
{
id: 'portone', name: 'portone della Pieve', box: [196, 160, 46, 56], at: [218, 222], face: 'up', exit: { scene: 'pieve_interno', at: [320, 352], face: 'up' },
},
{
id: 'campanile', name: 'campanile', box: [272, 8, 60, 140], at: [300, 300], face: 'up',
actions: {
@@ -277,20 +282,63 @@ export const story = {
},
},
{
id: 'verso_provinciale', name: 'Via Provinciale, verso il River', box: [30, 330, 200, 30], at: [130, 350], exit: { scene: 'via_provinciale', at: [100, 350], face: 'up' },
id: 'verso_provinciale', name: 'Via Provinciale, verso il River', box: [30, 330, 200, 30], at: [130, 350], exit: { scene: 'via_provinciale', at: [560, 240], face: 'left' },
},
{
id: 'verso_piazza', name: 'verso la Piazza', box: [0, 262, 26, 98], at: [8, 320], exit: { scene: 'piazza', at: [610, 320], face: 'left' },
},
{
id: 'verso_forno', name: 'a destra, verso il forno', box: [570, 176, 70, 76], at: [588, 250], exit: { scene: 'forno', at: [40, 330], face: 'right' },
},
{
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' },
},
],
},
// Dentro la Pieve (dalla foto di Mariano Mirannalti): la navata, l'altare, il crocifisso e San Pietro.
pieve_interno: {
name: 'Dentro la Pieve',
map: [56, 39],
background: 'assets/scenes/pieve-interno.png',
walk: [[262, 360], [290, 256], [350, 256], [380, 360]],
depth: [[256, 0.5], [360, 1.15]],
entry: [320, 352],
hotspots: [
{
id: 'altare', name: 'altare', box: [270, 186, 100, 64], at: [320, 262], face: 'up',
actions: {
look: [{ say: 'L\'altare, coi candelieri dorati e i fiori freschi. Qualcuno li cambia ogni mattina, prima di tutti.' }],
take: [{ say: 'Un candeliere? Mi sa che San Pietro le chiavi del Paradiso me le toglie per sempre.' }],
},
},
{
id: 'crocifisso', name: 'crocifisso', box: [296, 100, 48, 70], at: [320, 262], face: 'up',
actions: {
look: [{ say: 'Il grande crocifisso di legno, davanti alla statua di San Pietro.' }, { say: 'Qui si abbassa la voce senza che nessuno lo chieda.' }],
},
},
{
id: 'san_pietro_statua', name: 'statua di San Pietro', box: [298, 64, 44, 40], at: [320, 262], face: 'up',
actions: {
look: [{ say: 'San Pietro, il patrono. Quello fuori sul sagrato parla. Questo, per fortuna, no.' }],
},
},
{
id: 'panche', name: 'panche', box: [0, 240, 250, 120], at: [280, 320], face: 'left',
actions: {
look: [{ say: 'Le panche dove ci siamo seduti tutti, dal battesimo in poi. Le più scomode del Mugello.' }],
use: [{ say: 'Un minuto seduto... no, se mi siedo qui mi addormento come alla messa delle undici.' }],
},
},
{
id: 'uscita', name: 'esci sul sagrato', box: [240, 334, 160, 26], at: [320, 356], exit: { scene: 'pieve', at: [218, 222], face: 'down' },
},
],
},
// Il cortile dell'oratorio, dietro la porta della canonica: palco, tettoia, tavolate della festa.
oratorio: {
name: 'Oratorio della Pieve',
map: [74, 70],
map: [82, 45],
background: 'assets/scenes/oratorio.png',
walk: [[0, 236], [300, 226], [480, 222], [500, 232], [640, 254], [640, 360], [0, 360]],
depth: [[222, 0.5], [360, 1.15]],
@@ -340,7 +388,7 @@ export const story = {
// Via de' Medici: dalla Piazza sale verso Villa Adami (la Piazza è alle spalle, in fondo si va su).
via_de_medici: {
name: 'Via de\' Medici',
map: [14, 52],
map: [22, 32],
background: 'assets/scenes/via-de-medici.png',
walk: [[0, 360], [0, 336], [160, 290], [262, 240], [288, 212], [312, 208], [340, 222], [470, 300], [545, 360]],
depth: [[208, 0.32], [360, 1.25]],
@@ -364,7 +412,7 @@ export const story = {
// L'ingresso del campeggio: dalla Piazza a diritto; poco dopo, a destra, si sale al Bosco ai Frati.
campeggio: {
name: 'Ingresso del campeggio',
map: [16, 88],
map: [12, 84],
background: 'assets/scenes/campeggio.png',
walk: [[150, 360], [165, 262], [420, 266], [640, 286], [640, 360]],
depth: [[262, 0.6], [360, 1.15]],
@@ -395,7 +443,7 @@ export const story = {
// Il convento del Bosco ai Frati, nel bosco sopra il campeggio.
bosco_ai_frati: {
name: 'Bosco ai Frati',
map: [42, 88],
map: [12, 96],
background: 'assets/scenes/bosco-ai-frati.png',
walk: [[0, 304], [100, 298], [480, 292], [640, 300], [640, 360], [0, 360]],
depth: [[292, 0.78], [360, 1.1]],
@@ -431,7 +479,7 @@ export const story = {
// strada di fronte alla farmacia; da qui si scende ai giardini delle poste.
comune: {
name: 'Il Comune',
map: [6, 62],
map: [11, 45],
background: 'assets/scenes/comune-asilo.png',
walk: [[0, 360], [0, 262], [200, 256], [330, 250], [420, 268], [440, 305], [640, 330], [640, 360]],
depth: [[250, 0.5], [360, 1.15]],
@@ -464,14 +512,14 @@ export const story = {
id: 'ai_giardini', name: 'giù, verso i giardini', box: [596, 300, 44, 60], at: [620, 345], exit: { scene: 'giardini_poste', at: [580, 340], face: 'left' },
},
{
id: 'alla_farmacia', name: 'giù, verso la farmacia', box: [60, 334, 520, 26], at: [320, 354], exit: { scene: 'via_provinciale', at: [320, 345], face: 'up' },
id: 'alla_farmacia', name: 'giù, verso la farmacia', box: [60, 334, 520, 26], at: [320, 354], exit: { scene: 'via_provinciale', at: [622, 244], face: 'left' },
},
],
},
// I giardini delle poste: giù dal Comune; in fondo, le poste.
giardini_poste: {
name: 'Giardini delle poste',
map: [6, 78],
map: [11, 58],
background: 'assets/scenes/giardini-poste.png',
walk: [[0, 360], [0, 232], [640, 232], [640, 360]],
depth: [[232, 0.55], [360, 1.15]],
@@ -480,7 +528,7 @@ export const story = {
{
id: 'giochi', name: 'giochi', box: [196, 120, 330, 100], at: [360, 250], face: 'up',
actions: {
look: [{ say: 'Lo scivolo e le altalene. Sono troppo grande, ufficialmente. Ufficiosamente, un giro ci starebbe.' }],
look: [{ say: 'Lo scivolo e le altalene. Ho ventisette anni, ufficialmente. Ufficiosamente, un giro ci starebbe.' }],
use: [{ say: 'Un giro sull\'altalena! ... No, c\'è troppa gente che mi conosce.' }],
},
},
@@ -504,7 +552,7 @@ export const story = {
// Via Calimara: dalla Piazza scende alla farmacia, in Via Provinciale.
via_calimara: {
name: 'Via Calimara',
map: [26, 80],
map: [36, 58],
background: 'assets/scenes/via-calimara.png',
walk: [[0, 360], [0, 318], [330, 206], [400, 206], [560, 300], [600, 360]],
depth: [[206, 0.3], [360, 1.15]],
@@ -523,17 +571,17 @@ export const story = {
},
},
{
id: 'alla_provinciale', name: 'giù, verso la farmacia', box: [330, 176, 90, 40], at: [365, 210], exit: { scene: 'via_provinciale', at: [540, 345], face: 'up' },
id: 'alla_piazza', name: 'in fondo, la Piazza', box: [330, 176, 90, 40], at: [365, 210], exit: { scene: 'piazza', at: [360, 350], face: 'up' },
},
{
id: 'alla_piazza', name: 'su, verso la Piazza', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'piazza', at: [360, 350], face: 'up' },
id: 'alla_provinciale', name: 'verso la farmacia', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'via_provinciale', at: [600, 330], face: 'left' },
},
],
},
// Via Provinciale alla rotonda: il River e la farmacia. Andando avanti, la Pieve.
via_provinciale: {
name: 'Via Provinciale',
map: [36, 84],
map: [40, 71],
background: 'assets/scenes/via-provinciale.png',
walk: [[0, 360], [0, 312], [300, 302], [440, 262], [470, 238], [640, 238], [640, 360]],
depth: [[238, 0.45], [360, 1.15]],
@@ -554,26 +602,26 @@ export const story = {
},
},
{
id: 'area_feste', name: 'a sinistra, l\'area feste', box: [0, 250, 60, 110], at: [12, 330], exit: { scene: 'area_feste', at: [300, 350], face: 'up' },
id: 'al_ponte', name: 'a sinistra, verso il campo sportivo', box: [0, 230, 60, 130], at: [12, 320], exit: { scene: 'ponte_sieve', at: [300, 350], face: 'up' },
},
{
id: 'al_ponte', name: 'a diritto, verso il campo sportivo', box: [560, 222, 80, 46], at: [600, 248], exit: { scene: 'ponte_sieve', at: [300, 350], face: 'up' },
id: 'alla_pieve', name: 'dietro la farmacia, verso la Pieve', box: [470, 180, 120, 60], at: [560, 240], exit: { scene: 'pieve', at: [130, 345], face: 'right' },
},
{
id: 'alla_pieve', name: 'verso la Pieve', box: [0, 330, 213, 30], at: [100, 352], exit: { scene: 'pieve', at: [130, 345], face: 'right' },
id: 'area_feste', name: 'verso l\'area feste', box: [60, 330, 440, 30], at: [260, 352], exit: { scene: 'area_feste', at: [300, 350], face: 'up' },
},
{
id: 'al_comune', name: 'di fronte alla farmacia, verso il Comune', box: [213, 330, 214, 30], at: [320, 352], exit: { scene: 'comune', at: [320, 350], face: 'up' },
id: 'al_comune', name: 'verso il Comune', box: [592, 140, 48, 96], at: [622, 244], exit: { scene: 'comune', at: [320, 350], face: 'up' },
},
{
id: 'alla_calimara', name: 'Via Calimara, verso la Piazza', box: [427, 330, 213, 30], at: [540, 352], exit: { scene: 'via_calimara', at: [365, 210], face: 'down' },
id: 'alla_calimara', name: 'Via Calimara, verso la Piazza', box: [520, 260, 120, 100], at: [600, 330], exit: { scene: 'via_calimara', at: [300, 350], face: 'up' },
},
],
},
// L'area feste (il parco Antonio Berti): dalla rotonda del River, a sinistra.
area_feste: {
name: 'Area feste',
map: [40, 99],
map: [30, 84],
background: 'assets/scenes/area-feste.png',
walk: [[0, 360], [0, 250], [260, 190], [420, 190], [640, 240], [640, 360]],
depth: [[190, 0.35], [360, 1.15]],
@@ -593,14 +641,17 @@ export const story = {
},
},
{
id: 'alla_rotonda', name: 'indietro, verso la rotonda', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'via_provinciale', at: [12, 330], face: 'right' },
id: 'alla_polisportiva', name: 'in fondo, verso il campo sportivo', box: [360, 168, 110, 34], at: [400, 198], exit: { scene: 'polisportiva', at: [320, 265], face: 'down' },
},
{
id: 'alla_rotonda', name: 'indietro, verso la rotonda', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'via_provinciale', at: [260, 345], face: 'up' },
},
],
},
// Il ponte sulla Sieve, proseguendo la Provinciale dalla farmacia; di là dal ponte, la polisportiva.
ponte_sieve: {
name: 'Ponte sulla Sieve',
map: [30, 94],
map: [48, 84],
background: 'assets/scenes/ponte-sieve.png',
walk: [[0, 360], [0, 252], [180, 226], [300, 202], [342, 202], [380, 214], [330, 238], [330, 300], [460, 330], [640, 318], [640, 360]],
depth: [[202, 0.4], [360, 1.15]],
@@ -622,14 +673,14 @@ export const story = {
id: 'alla_polisportiva', name: 'di là dal ponte, il campo sportivo', box: [250, 180, 110, 36], at: [320, 206], exit: { scene: 'polisportiva_principale', at: [40, 330], face: 'right' },
},
{
id: 'alla_provinciale', name: 'indietro, verso la farmacia', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'via_provinciale', at: [600, 248], face: 'left' },
id: 'alla_provinciale', name: 'indietro, verso la farmacia', box: [0, 330, 640, 30], at: [300, 354], exit: { scene: 'via_provinciale', at: [12, 320], face: 'right' },
},
],
},
// La polisportiva: passato il ponte, prima il campo principale con la tribuna; proseguendo il secondario, coi campi da padel e tennis.
polisportiva: {
name: 'Polisportiva',
map: [12, 99],
name: 'Campo secondario',
map: [62, 96],
background: 'assets/scenes/polisportiva-secondario.png',
walk: [[0, 360], [0, 330], [300, 262], [430, 222], [600, 210], [640, 212], [640, 360]],
depth: [[210, 0.4], [360, 1.15]],
@@ -648,12 +699,22 @@ export const story = {
},
},
{
id: 'padel', name: 'campi da padel e da tennis', box: [520, 190, 120, 40], at: [600, 214], face: 'right', default: 'walk',
id: 'tennis', name: 'campi da tennis', box: [520, 190, 120, 40], at: [600, 214], face: 'right', default: 'walk',
actions: {
walk: [{ say: 'Più avanti ci sono i campi da tennis e da padel.' }, { say: 'Il padel, nel 2007, non sapevo nemmeno cosa fosse. Lo scopriranno i miei genitori, tra qualche anno.' }],
look: [{ say: 'Laggiù i campi da tennis e da padel. Racchette, palline e gente che si lamenta del sole.' }],
walk: [{ say: 'Più avanti ci sono i campi da tennis. Racchette, palline e gente che si lamenta del sole.' }],
look: [{ say: 'Laggiù i campi da tennis.' }],
},
},
{
id: 'padel', name: 'capannone del padel', box: [0, 110, 90, 180], at: [40, 345], face: 'left',
actions: {
look: [{ say: 'Il capannone verde del padel.' }, { say: 'Il padel, nel 2007, non so nemmeno cosa sia. Tra qualche anno ci giocheranno tutti, me compreso.' }],
open: [{ say: 'Chiuso. E poi non saprei nemmeno da che parte si tiene la racchetta.' }],
},
},
{
id: 'all_area_feste', name: 'dietro il campo, verso l\'area feste', box: [150, 130, 190, 50], at: [320, 265], exit: { scene: 'area_feste', at: [400, 198], face: 'down' },
},
{
id: 'al_principale', name: 'indietro, verso il campo principale', box: [0, 330, 640, 30], at: [100, 354], exit: { scene: 'polisportiva_principale', at: [620, 330], face: 'left' },
},
@@ -661,7 +722,7 @@ export const story = {
},
polisportiva_principale: {
name: 'Campo principale',
map: [22, 99],
map: [40, 96],
background: 'assets/scenes/polisportiva-principale.png',
walk: [[0, 298], [640, 298], [640, 360], [0, 360]],
depth: [[298, 1.0], [360, 1.2]],
@@ -688,10 +749,67 @@ export const story = {
},
],
},
// Via Provinciale, a destra dalla Pieve: il forno; poi a sinistra la stazione, avanti «alla Luna» e Campomigliaio.
forno: {
name: 'Il forno',
map: [86, 58],
background: 'assets/scenes/forno-conti.png',
walk: [[0, 360], [0, 268], [640, 268], [640, 360]],
depth: [[268, 0.8], [360, 1.15]],
entry: [40, 330],
hotspots: [
{
id: 'forno', name: 'forno', box: [100, 40, 420, 220], at: [320, 290], face: 'up',
actions: {
look: [{ say: 'Il forno. Il profumo del pane arriva fino alla Pieve, e la domenica mattina fino a casa mia.' }],
open: [{ say: 'Una schiacciata calda... no, prima il palio. Però l\'odore me lo porto dietro.' }],
},
},
{
id: 'alla_stazione', name: 'a sinistra, la stazione', box: [0, 220, 40, 110], at: [16, 300], exit: { scene: 'stazione', at: [320, 340], face: 'up' },
},
{
id: 'alla_luna', name: 'avanti, verso «alla Luna» e Campomigliaio', box: [600, 230, 40, 130], at: [630, 330], face: 'right', default: 'walk',
actions: {
walk: [{ say: 'Più avanti c\'è «alla Luna», e poi Campomigliaio, il mio rione: i blu.' }, { say: 'Ci torno quando ho ritrovato il palio. A mani vuote non mi ci faccio vedere.' }],
look: [{ say: 'La Provinciale va avanti verso «alla Luna» e Campomigliaio.' }],
},
},
{
id: 'alla_pieve', name: 'indietro, verso la Pieve', box: [60, 334, 520, 26], at: [320, 354], exit: { scene: 'pieve', at: [588, 250], face: 'left' },
},
],
},
stazione: {
name: 'La stazione',
map: [88, 71],
background: 'assets/scenes/stazione.png',
walk: [[0, 360], [0, 270], [640, 270], [640, 360]],
depth: [[270, 0.75], [360, 1.15]],
entry: [320, 340],
hotspots: [
{
id: 'stazione', name: 'stazione', box: [200, 30, 440, 200], at: [400, 290], face: 'up',
actions: {
look: [{ say: 'La stazione della Faentina. Da qui i treni vanno a Firenze e a Faenza, con calma.' }, { say: 'Molta calma.' }],
},
},
{
id: 'bar', name: 'bar della stazione', box: [360, 196, 50, 66], at: [385, 290], face: 'up',
actions: {
look: [{ say: 'Il bar della stazione. Qui si aspetta il treno, e quando il treno è in ritardo si aspetta un altro caffè.' }],
open: [{ say: 'Non ho tempo per un caffè. E poi il caffè mi fa tremare come la carretta del Palio.' }],
},
},
{
id: 'al_forno', name: 'indietro, verso il forno', box: [0, 330, 640, 30], at: [320, 354], exit: { scene: 'forno', at: [16, 320], face: 'right' },
},
],
},
// Livello 1: Villa Adami, vista dalla salita della Fortezza guardando in discesa.
villa_adami: {
name: 'Villa Adami',
map: [34, 48],
map: [46, 32],
background: 'assets/scenes/villa-adami.png',
walk: [[0, 272], [30, 246], [60, 244], [110, 256], [190, 268], [360, 296], [440, 288], [520, 276], [560, 268], [612, 262], [614, 272], [520, 288], [500, 292], [500, 360], [0, 360]],
depth: [[244, 0.5], [360, 1.2]],
@@ -718,7 +836,7 @@ export const story = {
// Tra il livello 1 e il 2: la salita di Villa Adami, con la casa del leone. Si sale alla Fortezza.
salita_villa_adami: {
name: 'Salita di Villa Adami',
map: [30, 32],
map: [38, 19],
background: 'assets/scenes/salita-villa-adami.png',
walk: [[0, 318], [100, 312], [282, 318], [300, 300], [318, 262], [336, 252], [420, 266], [640, 296], [640, 360], [0, 360]],
depth: [[252, 0.5], [360, 1.1]],
@@ -736,7 +854,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: [300, 350], face: 'up' },
actions: {
look: [{ say: 'Di là si va alla scuola. Ci passo già abbastanza tempo durante l\'anno.' }],
look: [{ say: 'Di là si va alla scuola. Ci ho passato abbastanza anni, grazie.' }],
},
},
{
@@ -750,7 +868,7 @@ export const story = {
// La strada da Pieve a Cardetole: il posticino della Carza, il ponticino, l'area camper.
carza: {
name: 'Il posticino della Carza',
map: [54, 80],
map: [60, 58],
background: 'assets/scenes/carza.png',
walk: [[60, 360], [250, 262], [300, 218], [350, 218], [420, 262], [600, 360]],
depth: [[218, 0.4], [360, 1.15]],
@@ -779,7 +897,7 @@ export const story = {
},
ponticino: {
name: 'Il ponticino sulla Carza',
map: [62, 86],
map: [64, 71],
background: 'assets/scenes/ponticino-carza.png',
walk: [[250, 360], [438, 198], [478, 198], [560, 360]],
depth: [[198, 0.3], [360, 1.15]],
@@ -808,7 +926,7 @@ export const story = {
},
area_camper: {
name: 'Area camper',
map: [70, 92],
map: [70, 84],
background: 'assets/scenes/area-camper.png',
walk: [[0, 360], [0, 260], [200, 232], [380, 240], [450, 248], [640, 300], [640, 360]],
depth: [[232, 0.5], [360, 1.15]],
@@ -840,7 +958,7 @@ export const story = {
// Il prato del parco dei camper visto dall'altra parte: per i ragazzi, «il campo del Giannini».
campo_giannini: {
name: 'Il campo del Giannini',
map: [66, 98],
map: [82, 96],
background: 'assets/scenes/campo-giannini.png',
walk: [[0, 360], [0, 214], [300, 208], [460, 210], [640, 220], [640, 360]],
depth: [[208, 0.4], [360, 1.15]],
@@ -867,7 +985,7 @@ export const story = {
// Cardetole, il rione dei verdi, dove abita Andrea: giù dalla Pieve.
cardetole: {
name: 'Cardetole',
map: [78, 96],
map: [90, 84],
background: 'assets/scenes/cardetole.png',
walk: [[0, 360], [0, 344], [120, 332], [200, 304], [262, 292], [330, 292], [372, 296], [640, 266], [640, 360]],
depth: [[266, 0.55], [360, 1.15]],
@@ -885,7 +1003,7 @@ export const story = {
id: 'platano', name: 'platano', box: [90, 0, 170, 216], at: [200, 306], face: 'up',
actions: {
look: [{ say: 'Il prato sotto casa. Qui le porte le facevamo con le maglie per terra: due maglie, un palo ciascuna.' }, { say: 'E ogni gol era «Era fuori!», «No, era dentro!».' }],
use: [{ say: 'Arrampicarmi? Mia mamma mi vede dalla finestra.' }],
use: [{ say: 'Arrampicarmi? Alla mia età, e davanti a tutto il condominio?' }],
},
},
{
@@ -896,7 +1014,7 @@ export const story = {
// L'ingresso della scuola, giù per la strada dalla salita di Villa Adami: dal cancello alle porte a vetri.
scuola: {
name: 'La scuola',
map: [8, 32],
map: [14, 19],
background: 'assets/scenes/scuola.png',
walk: [[110, 360], [118, 226], [140, 222], [268, 220], [290, 232], [520, 360]],
depth: [[220, 0.42], [360, 1.32]],
@@ -905,22 +1023,22 @@ export const story = {
{
id: 'pino', name: 'pino', box: [222, 40, 120, 70], at: [250, 232], face: 'up',
actions: {
look: [{ say: 'Un pino a ombrello enorme. Alla ricreazione, tutti all\'ombra lì sotto.' }],
look: [{ say: 'Un pino a ombrello enorme. Alla ricreazione, tutti all\'ombra lì sotto. Sembra ieri.' }],
},
},
{
id: 'bandiere', name: 'bandiere', box: [110, 110, 170, 52], at: [200, 230], face: 'up',
actions: {
look: [{ say: 'Il tricolore e la bandiera europea. Almeno loro a scuola ci stanno volentieri.' }],
take: [{ say: 'Rubare una bandiera davanti alla scuola? Poi chi la sente la preside.' }],
take: [{ say: 'Rubare una bandiera davanti alla scuola? Ci manca solo che mi richiamino in presidenza, a ventisette anni.' }],
},
},
{
id: 'porte', name: 'porte della scuola', box: [130, 164, 136, 56], at: [200, 230], face: 'up',
actions: {
look: [{ say: 'Le porte a vetri della scuola. Di là ci sono i banchi, la lavagna e la campanella.' }],
open: [{ say: 'A scuola? A maggio? Nemmeno per sogno.' }],
walk: [{ say: 'A scuola? A maggio? Nemmeno per sogno.' }],
open: [{ say: 'Tornare a scuola? A ventisette anni? Nemmeno per sogno.' }],
walk: [{ say: 'Tornare a scuola? A ventisette anni? Nemmeno per sogno.' }],
},
},
{
@@ -937,7 +1055,7 @@ export const story = {
// Il viale dei cipressi, la strada bianca tra la Fortezza e il Trebbio: in fondo c'è il castello.
viale_cipressi: {
name: 'Viale dei cipressi',
map: [55, 15],
map: [60, 6],
background: 'assets/scenes/viale-cipressi.png',
walk: [[30, 360], [200, 300], [288, 252], [312, 250], [420, 300], [600, 360]],
depth: [[250, 0.16], [360, 1.2]],
@@ -960,7 +1078,7 @@ export const story = {
},
trebbio: {
name: 'Castello del Trebbio',
map: [80, 15],
map: [84, 6],
background: 'assets/scenes/trebbio.png',
music: 'assets/audio/ricordi.mp3',
walk: [[86, 308], [300, 306], [356, 308], [470, 312], [500, 324], [486, 360], [88, 360], [80, 330]],
@@ -981,7 +1099,7 @@ export const story = {
id: 'pergolato', name: 'pergolato', box: [278, 222, 122, 52], at: [328, 312], face: 'up',
actions: {
look: [{ say: 'Il pergolato, coi suoi pilastri. Un giorno li conto tutti.' }],
take: [{ say: 'Una vite? Ci vuole la vendemmia, non un ragazzo.' }],
take: [{ say: 'Una vite? Ci vuole la vendemmia, non uno con le mani in tasca.' }],
},
},
{
+3 -2
View File
@@ -113,10 +113,11 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
#map-board button:hover { background: var(--accent); }
#map-board button.here { color: var(--ink); background: #2f5fb3; cursor: default; }
#map-board button.here::after { content: ' · sei qui'; font-weight: normal; }
#credits { z-index: 3; justify-content: flex-start; overflow-y: auto; padding-top: 4%; background: rgba(15, 10, 20, .96); }
/* I crediti crescono con chi contribuisce: scorre solo l'elenco, dedica e Chiudi restano sempre in vista. */
#credits { z-index: 3; justify-content: flex-start; overflow: hidden; padding-top: 4%; background: rgba(15, 10, 20, .96); }
#credits .credits-dedication { margin: .2em 0 .5em !important; font-size: clamp(16px, 2.6vw, 28px); }
/* Due colonne: il ruolo a destra della sua colonna, il nome a sinistra della sua, allineati sulla riga. */
#credits dl { margin: .2em 0 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 1em; row-gap: .45em; align-items: baseline; width: min(100%, 44em); }
#credits dl { flex: 1 1 auto; min-height: 0; overflow-y: auto; align-content: start; padding-right: .4em; margin: .2em 0 0; display: grid; grid-template-columns: 1fr 1fr; column-gap: 1em; row-gap: .45em; align-items: baseline; width: min(100%, 44em); }
#credits dt { text-align: right; color: var(--dim); font-size: clamp(10px, 1.4vw, 14px); letter-spacing: .12em; text-transform: uppercase; }
#credits dd { margin: 0; text-align: left; color: var(--ink); font-size: clamp(13px, 2vw, 20px); font-weight: bold; }
#credits .legal { color: var(--dim); font-size: clamp(10px, 1.2vw, 13px); opacity: .8; }
+8 -2
View File
@@ -64,8 +64,9 @@ try {
assert.match(await page.textContent('#credits'), /Yuri Baldi/);
assert.match(await page.textContent('#credits'), /Francesco Bacci/);
await page.screenshot({ path: `${OUT}/01c-crediti.png` });
const fitsCredits = await page.evaluate(() => { const c = document.getElementById('credits'); return c.scrollHeight <= c.clientHeight; });
assert.ok(fitsCredits, 'i crediti stanno in una schermata');
// L'elenco può scorrere; la dedica e il pulsante Chiudi devono restare dentro lo schermo.
const fitsCredits = await page.evaluate(() => { const c = document.getElementById('credits').getBoundingClientRect(), b = document.getElementById('close-credits').getBoundingClientRect(); return b.bottom <= c.bottom && b.top >= c.top; });
assert.ok(fitsCredits, 'il pulsante Chiudi dei crediti è in vista');
await page.keyboard.press('Escape');
assert.ok(await page.isHidden('#credits'), 'Esc chiude i crediti');
await page.click('#new-game');
@@ -84,6 +85,11 @@ try {
assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo');
await settle(page);
// Doppio clic su un punto della scena: Andrea ci va di corsa.
const r = await page.locator('#scene').boundingBox();
await page.mouse.dblclick(r.x + 300 / 640 * r.width, r.y + 330 / 360 * r.height);
assert.ok(await page.evaluate(() => game.hurry), 'il doppio clic fa correre');
await settle(page);
// Pausa: Esc o il pulsante fermano il gioco; Riprendi torna dov'eri; il titolo offre «Continua».
await page.keyboard.press('Escape');
await page.waitForSelector('#pause:not([hidden])');
+13
View File
@@ -182,3 +182,16 @@ test('i personaggi della scena: posa da fermi, battito di ciglia, parlato e posa
g.speech.pose = 'point';
assert.equal(g.personPose(p), 'point');
});
test('col doppio clic si cammina al doppio della velocità', () => {
const far = hurry => {
const g = new Game(mini());
g.go('a', [10, 50]);
g.hurry = hurry;
g.walkTo([190, 50]);
g.update(0.5);
return g.state.x - 10;
};
const slow = far(false), fast = far(true);
assert.ok(slow > 0 && Math.abs(fast - 2 * slow) < 1, `normale ${slow}, doppio ${fast}`);
});
+16
View File
@@ -42,3 +42,19 @@ test('mentre parla qualcuno la musica si abbassa, poi risale', () => {
for (let i = 0; i < 40; i++) m.update('g.mp3', 0.1);
assert.equal(m.current.audio.volume, 0.6);
});
test('in pausa la musica sfuma fino a zero e si ferma', () => {
const played = [], paused = [];
globalThis.Audio = class { constructor(src) { this.src = src; this.volume = 0; this.paused = true; }
play() { this.paused = false; played.push(this.src); return Promise.resolve(); } pause() { this.paused = true; paused.push(this.src); } };
const m = new Music({ volume: 0.5 });
for (let i = 0; i < 40; i++) m.update('a.mp3', 0.1);
assert.equal(m.current.audio.volume, 0.5);
m.hold = true;
for (let i = 0; i < 40; i++) m.update('a.mp3', 0.1);
assert.ok(m.current.audio.paused && m.current.audio.volume === 0, 'ferma a volume zero');
m.hold = false;
m.update('a.mp3', 0.1);
assert.ok(!m.current.audio.paused && played.length === 2, 'riprende lo stesso brano');
delete globalThis.Audio;
});