Il risveglio non rallenta più il gioco: la nebbia è lo sfondo sfocato disegnato sopra, senza filtri (ctx.filter e il filter CSS sulla tela facevano andare a passo d'uomo tutto, anche dopo), e l'iframe del muro c'è solo a muro aperto; le prove nel browser aspettano la fine del risveglio e l'avvio del gioco
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -15,7 +15,6 @@
|
|||||||
<div id="app">
|
<div id="app">
|
||||||
<div id="stage">
|
<div id="stage">
|
||||||
<canvas id="scene" width="640" height="360" aria-label="La scena dell'avventura"></canvas>
|
<canvas id="scene" width="640" height="360" aria-label="La scena dell'avventura"></canvas>
|
||||||
<iframe id="muro" hidden title="Il Muro del Sanpierino"></iframe>
|
|
||||||
<div id="speech" aria-live="polite"></div>
|
<div id="speech" aria-live="polite"></div>
|
||||||
<button id="closeup-close" hidden title="Chiudi (Esc)">Chiudi</button>
|
<button id="closeup-close" hidden title="Chiudi (Esc)">Chiudi</button>
|
||||||
<div id="corner">
|
<div id="corner">
|
||||||
|
|||||||
@@ -924,7 +924,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.
|
||||||
if (def.type === 'risveglio') { drawRisveglio(ctx, c); return; }
|
if (def.type === 'risveglio') { drawRisveglio(ctx, c, backgrounds.get(game.backdrop())); 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); };
|
||||||
|
|||||||
+42
-28
@@ -30,24 +30,37 @@ 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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
let scratch = null;
|
// La nebbia è lo sfondo rimpicciolito e riallargato morbido, sopra la scena: niente filtri. Sfocare con ctx.filter
|
||||||
export function drawRisveglio(ctx, c) {
|
// (rileggendo la tela) o col filter CSS sulla tela rallentava tutto il gioco anche a risveglio finito.
|
||||||
|
const SMALL = [80, 45];
|
||||||
|
let small = null, smallOf = null;
|
||||||
|
function blurred(bg) {
|
||||||
|
if (smallOf !== bg) {
|
||||||
|
small ??= Object.assign(document.createElement('canvas'), { width: SMALL[0], height: SMALL[1] });
|
||||||
|
const s = small.getContext('2d');
|
||||||
|
s.imageSmoothingEnabled = true;
|
||||||
|
s.drawImage(bg, 0, 0, ...SMALL);
|
||||||
|
smallOf = bg;
|
||||||
|
}
|
||||||
|
return small;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
// La scena annebbiata: sfocata, e una seconda copia spostata che ondeggia (la vista doppia).
|
// La scena annebbiata (lo sfondo sfocato sopra) e la vista doppia (una seconda copia, spostata, che ondeggia).
|
||||||
if (h > 0.01 && typeof document !== 'undefined') {
|
if (h > 0.01 && bg && typeof document !== 'undefined') {
|
||||||
if (!scratch) { scratch = document.createElement('canvas'); scratch.width = W; scratch.height = H; }
|
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;
|
||||||
const s = scratch.getContext('2d');
|
ctx.imageSmoothingEnabled = true;
|
||||||
s.clearRect(0, 0, W, H);
|
ctx.globalAlpha = 0.85 * h;
|
||||||
s.drawImage(ctx.canvas, 0, 0, W, H);
|
ctx.drawImage(fog, 0, 0, W, H);
|
||||||
const sway = 14 * h, dx = Math.sin(c.age * 1.3) * sway, dy = Math.sin(c.age * 0.9 + 1) * sway * 0.35;
|
ctx.globalAlpha = 0.4 * h;
|
||||||
ctx.filter = `blur(${(5 * h).toFixed(2)}px)`;
|
ctx.drawImage(fog, dx, dy, W, H);
|
||||||
ctx.drawImage(scratch, 0, 0);
|
|
||||||
ctx.globalAlpha = 0.45 * h;
|
|
||||||
ctx.drawImage(scratch, dx, dy);
|
|
||||||
ctx.globalAlpha = 1;
|
ctx.globalAlpha = 1;
|
||||||
ctx.filter = 'none';
|
ctx.imageSmoothingEnabled = false;
|
||||||
// La luce del mattino che abbaglia.
|
}
|
||||||
|
// La luce del mattino che abbaglia.
|
||||||
|
if (h > 0.01) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
@@ -57,20 +70,21 @@ export function drawRisveglio(ctx, c) {
|
|||||||
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 morbido; chiuse, si vede il rosso scuro della luce che passa.
|
// Le palpebre: sopra e sotto, col bordo curvo; il bordo morbido sono quattro passate sempre più chiuse e più
|
||||||
|
// scure. Chiuse, si vede il rosso scuro della luce che passa.
|
||||||
if (open < 0.999) {
|
if (open < 0.999) {
|
||||||
const gap = 200 * open, mid = H / 2, bulge = 70 + 60 * open;
|
const mid = H / 2, lid = (gap, bulge, sign) => {
|
||||||
ctx.filter = 'blur(6px)';
|
const edge = mid + sign * gap, side = edge - sign * bulge, far = sign < 0 ? -20 : H + 20;
|
||||||
ctx.fillStyle = 'rgb(22,4,6)';
|
ctx.beginPath();
|
||||||
ctx.beginPath();
|
ctx.moveTo(-20, far); ctx.lineTo(W + 20, far); ctx.lineTo(W + 20, side);
|
||||||
ctx.moveTo(-20, -20); ctx.lineTo(W + 20, -20); ctx.lineTo(W + 20, mid - gap + bulge);
|
ctx.quadraticCurveTo(W / 2, edge * 2 - side, -20, side);
|
||||||
ctx.quadraticCurveTo(W / 2, mid - gap - bulge, -20, mid - gap + bulge);
|
ctx.closePath(); ctx.fill();
|
||||||
ctx.closePath(); ctx.fill();
|
};
|
||||||
ctx.beginPath();
|
for (let i = 0; i < 4; i++) {
|
||||||
ctx.moveTo(-20, H + 20); ctx.lineTo(W + 20, H + 20); ctx.lineTo(W + 20, mid + gap - bulge);
|
const gap = 200 * open + (3 - i) * 4, bulge = 70 + 60 * open;
|
||||||
ctx.quadraticCurveTo(W / 2, mid + gap + bulge, -20, mid + gap - bulge);
|
ctx.fillStyle = `rgba(22,4,6,${i === 3 ? 1 : 0.3})`;
|
||||||
ctx.closePath(); ctx.fill();
|
lid(gap, bulge, -1); lid(gap, bulge, 1);
|
||||||
ctx.filter = 'none';
|
}
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -1098,12 +1098,16 @@ function updateUi() {
|
|||||||
setProp($('spots-button'), 'hidden', !running || !!game.closeup);
|
setProp($('spots-button'), 'hidden', !running || !!game.closeup);
|
||||||
setProp($('notebook-button'), 'hidden', !running || !story.notebook || !!game.closeup);
|
setProp($('notebook-button'), 'hidden', !running || !story.notebook || !!game.closeup);
|
||||||
setProp($('closeup-close'), 'hidden', !running || !game.closeup);
|
setProp($('closeup-close'), 'hidden', !running || !game.closeup);
|
||||||
// Il Muro del Sanpierino: a muro aperto sul telefonino del Barone, la pagina vera sopra la scena. Ogni volta che si
|
// Il Muro del Sanpierino: a muro aperto sul telefonino del Barone, la pagina vera sopra la scena. L'iframe c'è solo
|
||||||
// riapre si ricarica (i messaggi nuovi della storia).
|
// allora (sempre presente, anche nascosto, rallentava la pagina): ogni volta che si riapre si ricarica, coi
|
||||||
const muro = $('muro'), wall = running && c && story.closeups[c.id]?.type === 'telefono' && c.step === 'wall';
|
// messaggi nuovi della storia.
|
||||||
if (wall && muro.hidden) muro.src = versioned('assets/muro/index.html');
|
const wall = running && c && story.closeups[c.id]?.type === 'telefono' && c.step === 'wall';
|
||||||
if (!wall && !muro.hidden) muro.src = 'about:blank';
|
let muro = $('muro');
|
||||||
setProp(muro, 'hidden', !wall);
|
if (wall && !muro) {
|
||||||
|
muro = Object.assign(document.createElement('iframe'), { id: 'muro', title: 'Il Muro del Sanpierino', src: versioned('assets/muro/index.html') });
|
||||||
|
canvas.after(muro);
|
||||||
|
}
|
||||||
|
if (!wall && muro) muro.remove();
|
||||||
if (wall) muro.style.setProperty('--muro-scale', String(canvas.clientWidth / 1067));
|
if (wall) muro.style.setProperty('--muro-scale', String(canvas.clientWidth / 1067));
|
||||||
setText($('closeup-close'), torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : mg ? c.step === 'result' ? mg.back : 'Lascia stare' : c && story.closeups[c.id]?.type === 'risveglio' ? 'Salta' : 'Chiudi');
|
setText($('closeup-close'), torch ? c.step === 'done' ? 'Continua' : 'Torna fuori' : mg ? c.step === 'result' ? mg.back : 'Lascia stare' : c && story.closeups[c.id]?.type === 'risveglio' ? 'Salta' : 'Chiudi');
|
||||||
setProp($('panel'), 'inert', !!c);
|
setProp($('panel'), 'inert', !!c);
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ body { display: flex; justify-content: center; overflow-x: hidden; -webkit-tap-h
|
|||||||
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;
|
||||||
transform: scale(var(--muro-scale, 0.6)); transform-origin: 0 0; }
|
transform: scale(var(--muro-scale, 0.6)); transform-origin: 0 0; }
|
||||||
#muro[hidden] { display: none; }
|
|
||||||
/* Il primo piano del libro si chiude da qui (o con Esc). */
|
/* Il primo piano del libro si chiude da qui (o con Esc). */
|
||||||
#closeup-close { position: absolute; right: 10px; bottom: 10px; z-index: 2; font: bold clamp(10px, 1.5vw, 15px) var(--font);
|
#closeup-close { position: absolute; right: 10px; bottom: 10px; z-index: 2; font: bold clamp(10px, 1.5vw, 15px) var(--font);
|
||||||
padding: .45em 1.2em; border-radius: 6px; border: 0; cursor: pointer; color: var(--bg); background: var(--accent); }
|
padding: .45em 1.2em; border-radius: 6px; border: 0; cursor: pointer; color: var(--bg); background: var(--accent); }
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ try {
|
|||||||
// Il cartello d'apertura, poi da solo il titolo.
|
// Il cartello d'apertura, poi da solo il titolo.
|
||||||
assert.match(await page.textContent('#splash'), /Manzolo\s*\(Poca\)\s*Art\s*Presents/);
|
assert.match(await page.textContent('#splash'), /Manzolo\s*\(Poca\)\s*Art\s*Presents/);
|
||||||
assert.ok(await page.isHidden('#title'), 'il titolo aspetta la fine del cartello');
|
assert.ok(await page.isHidden('#title'), 'il titolo aspetta la fine del cartello');
|
||||||
|
// Il gioco parte con un import() dinamico, che l'evento load della pagina non aspetta.
|
||||||
|
await page.waitForFunction(() => window.jingle, null, { timeout: 10000 });
|
||||||
assert.match(await page.evaluate(() => jingle.src), /assets\/audio\/cartello\.mp3\?v=\d+\.\d+\.\d+$/, 'la musichetta del cartello');
|
assert.match(await page.evaluate(() => jingle.src), /assets\/audio\/cartello\.mp3\?v=\d+\.\d+\.\d+$/, 'la musichetta del cartello');
|
||||||
assert.equal(await page.evaluate(() => music.want), null, 'la musica del titolo aspetta il titolo');
|
assert.equal(await page.evaluate(() => music.want), null, 'la musica del titolo aspetta il titolo');
|
||||||
await page.waitForTimeout(2800);
|
await page.waitForTimeout(2800);
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ export async function checkMultimedia(browser, port, out, errors) {
|
|||||||
await page.click('#splash'); await page.click('#new-game');
|
await page.click('#splash'); await page.click('#new-game');
|
||||||
await page.click('[data-rione="campomigliaio"]'); await page.click('#skip-intro');
|
await page.click('[data-rione="campomigliaio"]'); await page.click('#skip-intro');
|
||||||
const settle = async () => {
|
const settle = async () => {
|
||||||
for (let i = 0; i < 200; i++) {
|
for (let i = 0; i < 600; i++) {
|
||||||
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
||||||
if (await page.evaluate(() => game.closeup || game.idle)) return;
|
// Il risveglio dell'inizio è un primo piano che va avanti da solo: si aspetta che finisca.
|
||||||
|
if (await page.evaluate(() => (game.closeup && game.story.closeups[game.closeup.id].type !== 'risveglio') || game.idle)) return;
|
||||||
await page.waitForTimeout(30);
|
await page.waitForTimeout(30);
|
||||||
}
|
}
|
||||||
throw new Error('la sequenza non si ferma');
|
throw new Error('la sequenza non si ferma');
|
||||||
@@ -56,10 +57,10 @@ export async function checkMultimedia(browser, port, out, errors) {
|
|||||||
await page.waitForFunction(() => game.closeup?.id === 'fessura');
|
await page.waitForFunction(() => game.closeup?.id === 'fessura');
|
||||||
};
|
};
|
||||||
await openTorch();
|
await openTorch();
|
||||||
const saved = await page.evaluate(() => game.savable());
|
|
||||||
await page.keyboard.press('Escape'); await settle();
|
await page.keyboard.press('Escape'); await settle();
|
||||||
assert.equal(await page.evaluate(() => game.state.ended), false, 'uscire presto non vince');
|
assert.equal(await page.evaluate(() => game.state.ended), false, 'uscire presto non vince');
|
||||||
await openTorch();
|
await openTorch();
|
||||||
|
const saved = await page.evaluate(() => game.savable());
|
||||||
await page.click('#pause-button');
|
await page.click('#pause-button');
|
||||||
const age = await page.evaluate(() => game.closeup.age);
|
const age = await page.evaluate(() => game.closeup.age);
|
||||||
await page.waitForTimeout(150);
|
await page.waitForTimeout(150);
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ try {
|
|||||||
// «Home» torna alla home del sito sul telefonino.
|
// «Home» torna alla home del sito sul telefonino.
|
||||||
await frame.locator('.pagerbutton', { hasText: 'Home' }).first().click();
|
await frame.locator('.pagerbutton', { hasText: 'Home' }).first().click();
|
||||||
await page.waitForFunction(() => game.closeup.step === 'site', null, { timeout: 5000 });
|
await page.waitForFunction(() => game.closeup.step === 'site', null, { timeout: 5000 });
|
||||||
await page.waitForSelector('#muro', { state: 'hidden', timeout: 3000 });
|
await page.waitForSelector('#muro', { state: 'detached', timeout: 3000 });
|
||||||
assert.deepEqual(errors, []);
|
assert.deepEqual(errors, []);
|
||||||
console.log(`ok: schermate in ${out}`);
|
console.log(`ok: schermate in ${out}`);
|
||||||
} finally { await browser?.close(); server.kill(); }
|
} finally { await browser?.close(); server.kill(); }
|
||||||
|
|||||||
Reference in New Issue
Block a user