Il risveglio leggero per davvero: palpebre disegnate su una tela piccola e allargate con l'interpolazione (bordo morbido senza filtri né passate), tutto sulla tela principale, una versione sola; l'arretrato grafico dopo il risveglio passa da 24 secondi a un decimo. «Fanfara» si legge senza accenti (Andrea)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-10-31 13:40:43 +01:00
co-authored by Claude Fable 5.1
parent 4d2278a8f8
commit b10f031133
10 changed files with 35 additions and 62 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -4
View File
@@ -1984,8 +1984,8 @@
"sec": 2.79 "sec": 2.79
}, },
"andrea|La fanfara degli alpini. Quando sfilavano per il paese suonando, s'affacciava tutto San Piero.": { "andrea|La fanfara degli alpini. Quando sfilavano per il paese suonando, s'affacciava tutto San Piero.": {
"file": "andrea-9904de279c.mp3", "file": "andrea-a372a71849.mp3",
"sec": 6.69 "sec": 6.3
}, },
"fanfara|Un'altra? Questa la sanno anche i sassi!": { "fanfara|Un'altra? Questa la sanno anche i sassi!": {
"file": "fanfara-d1179f98fa.mp3", "file": "fanfara-d1179f98fa.mp3",
@@ -3676,8 +3676,8 @@
"sec": 5.94 "sec": 5.94
}, },
"andrea|La Smarronata del 1993! Gli alpini, sotto il ponte: le bruciate, la polenta, il vino, la fanfara...": { "andrea|La Smarronata del 1993! Gli alpini, sotto il ponte: le bruciate, la polenta, il vino, la fanfara...": {
"file": "andrea-2c7369689a.mp3", "file": "andrea-4266372068.mp3",
"sec": 10.99 "sec": 10.39
}, },
"andrea|La macchina del tempo è sempre lì che aspetta.": { "andrea|La macchina del tempo è sempre lì che aspetta.": {
"file": "andrea-81cad76b69.mp3", "file": "andrea-81cad76b69.mp3",
+1 -19
View File
@@ -182,7 +182,6 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
: drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null); : drawAndrea(ctx, s.x, s.y, game.scaleAt(s.y), s.facing, game.walking ? game.stride / 6 : null);
// Durante il risveglio si guarda dagli occhi di Andrea: lui non si vede. // Durante il risveglio si guarda dagli occhi di Andrea: lui non si vede.
const pov = game.closeup && game.story.closeups?.[game.closeup.id]?.type === 'risveglio'; const pov = game.closeup && game.story.closeups?.[game.closeup.id]?.type === 'risveglio';
if (!pov) risveglioLayer(ctx, false);
const queue = pov ? [] : [{ y: s.y, draw: drawAndreaNow }]; const queue = pov ? [] : [{ y: s.y, draw: drawAndreaNow }];
for (const p of game.people()) { for (const p of game.people()) {
const sheet = sprites.get(p.who); const sheet = sprites.get(p.who);
@@ -912,18 +911,6 @@ function drawDebug(ctx, game, { mouse, points = [], help }) {
ctx.restore(); ctx.restore();
} }
// La tela del risveglio (risveglio.js): una tela a parte sopra la scena, che c'è solo finché il risveglio dura.
function risveglioLayer(ctx, on = true) {
if (typeof document === 'undefined' || !ctx.canvas?.after) return null;
let layer = document.getElementById('risveglio');
if (!on) { layer?.remove(); return null; }
if (!layer) {
layer = Object.assign(document.createElement('canvas'), { id: 'risveglio', width: WIDTH, height: HEIGHT });
ctx.canvas.after(layer);
}
return layer.getContext('2d');
}
// --- Il primo piano di un libro (story.closeups, game.closeup): la scena si oscura, le mani alzano il libro dal basso. // --- Il primo piano di un libro (story.closeups, game.closeup): la scena si oscura, le mani alzano il libro dal basso.
// Le immagini hanno etichetta e pagine vuote: i testi si scrivono qui, nitidi. Una pagina sola gira, a fisarmonica // Le immagini hanno etichetta e pagine vuote: i testi si scrivono qui, nitidi. Una pagina sola gira, a fisarmonica
// sulla piega; sotto c'è già l'ultima pagina. // sulla piega; sotto c'è già l'ultima pagina.
@@ -962,12 +949,7 @@ function drawCloseup(ctx, game, time) {
// L'incantesimo va sopra la scena (con Andrea dentro), già disegnata. // L'incantesimo va sopra la scena (con Andrea dentro), già disegnata.
if (def.type === 'incanto') { drawIncanto(ctx, c); return; } if (def.type === 'incanto') { drawIncanto(ctx, c); return; }
// Il risveglio, visto da dentro gli occhi: anche lui sopra la scena già disegnata. // Il risveglio, visto da dentro gli occhi: anche lui sopra la scena già disegnata.
// Sul telefono (schermo touch) la versione leggera, senza filtri; sul computer quella con la sfocatura vera. if (def.type === 'risveglio') { drawRisveglio(ctx, c, backgrounds.get(game.backdrop())); return; }
if (def.type === 'risveglio') {
const layer = risveglioLayer(ctx), light = matchMedia('(pointer: coarse)').matches;
if (layer) drawRisveglio(layer, c, backgrounds.get(game.backdrop()), { light });
return;
}
// Il telefonino del Barone: sale come il libro, e il sito nello schermo si allarga a tutta la scena. // Il telefonino del Barone: sale come il libro, e il sito nello schermo si allarga a tutta la scena.
if (def.type === 'telefono') { if (def.type === 'telefono') {
const pic = src => { if (!backgrounds.has(src)) loadBackground(src); return backgrounds.get(src); }; const pic = src => { if (!backgrounds.has(src)) loadBackground(src); return backgrounds.get(src); };
+29 -34
View File
@@ -30,12 +30,12 @@ export function updateRisveglio(c, def, dt) {
return { due, end: c.t >= END && c.fired >= lines.length }; return { due, end: c.t >= END && c.fired >= lines.length };
} }
// Il risveglio si disegna su una tela sua, appoggiata sopra la scena solo finché dura (render.js, `#risveglio`). // Niente filtri e niente passate a tutto schermo (ctx.filter, il filter CSS e sedici palpebre trasparenti a ogni
// Due versioni (Andrea, 31 ottobre): sul computer quella bella, con la sfocatura vera (ctx.filter); sul telefono // fotogramma lasciavano alla grafica un arretrato che si pagava dopo, a risveglio finito: il gioco si inceppava).
// (`light`) una leggera senza filtri, perché senza una scheda video la sfocatura a ogni fotogramma pesa: la nebbia è // La nebbia è lo sfondo rimpicciolito e riallargato morbido; le palpebre si disegnano nette su una tela piccola e
// lo sfondo rimpicciolito e riallargato morbido, il bordo delle palpebre tante passate trasparenti. // si allargano di cinque volte con l'interpolazione, che sfuma il bordo da sé. Una versione sola, computer e telefono.
const SMALL = [80, 45]; const SMALL = [80, 45], LIDS = [128, 72];
let small = null, smallOf = null; let small = null, smallOf = null, lids = null;
function blurred(bg) { function blurred(bg) {
if (smallOf !== bg) { if (smallOf !== bg) {
small ??= Object.assign(document.createElement('canvas'), { width: SMALL[0], height: SMALL[1] }); small ??= Object.assign(document.createElement('canvas'), { width: SMALL[0], height: SMALL[1] });
@@ -47,23 +47,19 @@ function blurred(bg) {
return small; return small;
} }
export function drawRisveglio(ctx, c, bg, { light = false } = {}) { export function drawRisveglio(ctx, c, bg) {
const t = c.t, h = haze(t), open = openness(t), W = 640, H = 360; const t = c.t, h = haze(t), open = openness(t), W = 640, H = 360;
ctx.save(); ctx.save();
ctx.clearRect(0, 0, W, H); // La scena annebbiata (lo sfondo sfocato sopra) e la vista doppia (una seconda copia, spostata, che ondeggia).
// La scena annebbiata: lo sfondo sfocato sopra a quello nitido, più una seconda copia spostata che ondeggia (la if (h > 0.01 && bg && typeof document !== 'undefined') {
// vista doppia); tutto si dirada col passare del tempo. const fog = blurred(bg), sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
if (h > 0.01 && bg) {
const sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
const fog = light ? blurred(bg) : bg;
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingEnabled = true;
if (!light) ctx.filter = `blur(${(5 * h).toFixed(2)}px)`; ctx.globalAlpha = 0.85 * h;
ctx.globalAlpha = h;
ctx.drawImage(fog, 0, 0, W, H); ctx.drawImage(fog, 0, 0, W, H);
ctx.globalAlpha = 0.45 * h; ctx.globalAlpha = 0.4 * h;
ctx.drawImage(fog, dx, dy, W, H); ctx.drawImage(fog, dx, dy, W, H);
ctx.globalAlpha = 1; ctx.globalAlpha = 1;
ctx.filter = 'none'; ctx.imageSmoothingEnabled = false;
// La luce del mattino che abbaglia. // La luce del mattino che abbaglia.
ctx.fillStyle = `rgba(255,244,214,${0.22 * h})`; ctx.fillStyle = `rgba(255,244,214,${0.22 * h})`;
ctx.fillRect(0, 0, W, H); ctx.fillRect(0, 0, W, H);
@@ -74,23 +70,22 @@ export function drawRisveglio(ctx, c, bg, { light = false } = {}) {
v.addColorStop(1, `rgba(10,2,4,${0.85 * (1 - open * 0.75) * Math.max(h, 0.3)})`); v.addColorStop(1, `rgba(10,2,4,${0.85 * (1 - open * 0.75) * Math.max(h, 0.3)})`);
ctx.fillStyle = v; ctx.fillStyle = v;
ctx.fillRect(0, 0, W, H); ctx.fillRect(0, 0, W, H);
// Le palpebre: sopra e sotto, col bordo curvo e sfumato; chiuse, si vede il rosso scuro della luce che passa. // Le palpebre: sopra e sotto, col bordo curvo; chiuse, si vede il rosso scuro della luce che passa.
if (open < 0.999) { if (open < 0.999 && typeof document !== 'undefined') {
const mid = H / 2, bulge = 70 + 60 * open, lid = (gap, sign) => { lids ??= Object.assign(document.createElement('canvas'), { width: LIDS[0], height: LIDS[1] });
const edge = mid + sign * gap, side = edge - sign * bulge, far = sign < 0 ? -20 : H + 20; const l = lids.getContext('2d'), k = LIDS[0] / W, mid = H / 2 * k, gap = 200 * open * k, bulge = (70 + 60 * open) * k;
ctx.beginPath(); l.clearRect(0, 0, ...LIDS);
ctx.moveTo(-20, far); ctx.lineTo(W + 20, far); ctx.lineTo(W + 20, side); l.fillStyle = 'rgb(22,4,6)';
ctx.quadraticCurveTo(W / 2, edge * 2 - side, -20, side); for (const sign of [-1, 1]) {
ctx.closePath(); ctx.fill(); const edge = mid + sign * gap, side = edge - sign * bulge, far = sign < 0 ? -4 : LIDS[1] + 4;
}; l.beginPath();
if (light) { l.moveTo(-4, far); l.lineTo(LIDS[0] + 4, far); l.lineTo(LIDS[0] + 4, side);
// Il bordo morbido: passate trasparenti che sporgono verso l'occhio aperto, un pixel di più l'una. l.quadraticCurveTo(LIDS[0] / 2, edge * 2 - side, -4, side);
ctx.fillStyle = 'rgba(22,4,6,0.11)'; l.closePath(); l.fill();
for (let i = 16; i > 0; i--) { lid(200 * open - i, -1); lid(200 * open - i, 1); } }
} else ctx.filter = 'blur(6px)'; ctx.imageSmoothingEnabled = true;
ctx.fillStyle = 'rgb(22,4,6)'; ctx.drawImage(lids, 0, 0, W, H);
lid(200 * open, -1); lid(200 * open, 1); ctx.imageSmoothingEnabled = false;
ctx.filter = 'none';
} }
ctx.restore(); ctx.restore();
} }
-2
View File
@@ -82,8 +82,6 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
text-shadow: 2px 0 #000, -2px 0 #000, 0 2px #000, 0 -2px #000, 2px 2px #000, -2px -2px #000; } text-shadow: 2px 0 #000, -2px 0 #000, 0 2px #000, 0 -2px #000, 2px 2px #000, -2px -2px #000; }
#speech:empty { display: none; } #speech:empty { display: none; }
#speech { z-index: 3; } #speech { z-index: 3; }
/* La tela del risveglio, sopra la scena solo finché dura (render.js). */
#risveglio { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; }
/* Il Muro del Sanpierino sul telefonino del Barone: la pagina vera (assets/muro) larga 1067 pixel come la home del /* Il Muro del Sanpierino sul telefonino del Barone: la pagina vera (assets/muro) larga 1067 pixel come la home del
sito, rimpicciolita sulla scena da main.js (--muro-scale). */ sito, rimpicciolita sulla scena da main.js (--muro-scale). */
#muro { position: absolute; left: 0; top: 0; width: 1067px; height: 600px; border: 0; z-index: 1; background: #f7e1c0; #muro { position: absolute; left: 0; top: 0; width: 1067px; height: 600px; border: 0; z-index: 1; background: #f7e1c0;
-1
View File
@@ -28,7 +28,6 @@ try {
} }
await page.waitForFunction(() => !game.closeup && game.state.flags.intro, null, { timeout: 15000 }); await page.waitForFunction(() => !game.closeup && game.state.flags.intro, null, { timeout: 15000 });
await page.screenshot({ path: `${out}/${tag}-5-vecchietti.png` }); await page.screenshot({ path: `${out}/${tag}-5-vecchietti.png` });
assert.equal(await page.locator('#risveglio').count(), 0, 'finito il risveglio, la sua tela se ne va');
} }
assert.deepEqual(errors, []); assert.deepEqual(errors, []);
console.log(`ok: schermate in ${out}`); console.log(`ok: schermate in ${out}`);
+1 -2
View File
@@ -21,6 +21,5 @@
"Libici": "Lìbici", "Libici": "Lìbici",
"mancano": "màncano", "mancano": "màncano",
"console": "consòl", "console": "consòl",
"Mahhh... è l'INGORGOOOO!!!": "Maaaa, è l'Ingorgo! L'Ingorgooo!", "Mahhh... è l'INGORGOOOO!!!": "Maaaa, è l'Ingorgo! L'Ingorgooo!"
"fanfara": "fanfàra"
} }