From 73a8be39c56d4ad7b0c5dc7c212f82e317682992 Mon Sep 17 00:00:00 2001 From: Andrea Manzi Date: Sat, 24 Oct 2026 22:33:24 +0200 Subject: [PATCH] Al Trebbio parla lo spirito del castello: compare accanto alla torre, semitrasparente, con la lanterna Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh --- assets/characters/spirito-trebbio.png | Bin 0 -> 23171 bytes src/engine/render.js | 24 ++++++++++++++++++++++++ src/story/story.js | 6 ++++-- tests/render.test.mjs | 10 ++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 assets/characters/spirito-trebbio.png diff --git a/assets/characters/spirito-trebbio.png b/assets/characters/spirito-trebbio.png new file mode 100644 index 0000000000000000000000000000000000000000..b48c9fb226dbe14ef5b14d17f1356ae982b226a7 GIT binary patch literal 23171 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 drawSprite(ctx, sheet, game.personPose(p), ...p.at, game.scaleAt(p.at[1]), p.facing ?? 1, p.size) }); } for (const q of queue.sort((a, b) => a.y - b.y)) q.draw(); + drawApparitions(ctx, game, time); drawLights(ctx, scene, time); for (const shape of art) if (shape.top) drawShape(ctx, shape); if (spots) drawSpots(ctx, game); @@ -172,6 +173,29 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now( } } +// Le apparizioni (`apparitions`): uno spirito che compare solo mentre parla (un dialogo suo, o una sua battuta), +// semitrasparente, che ondeggia piano, con un alone freddo intorno e il caldo della lanterna. +function drawApparitions(ctx, game, time) { + for (const a of game.scene.apparitions ?? []) { + if (game.dialogue?.id !== a.who && game.speech?.who !== a.who) continue; + const img = backgrounds.get(a.src); + if (!img) { loadBackground(a.src); continue; } + const h = a.height, w = Math.round(img.width * h / img.height), bob = Math.round(Math.sin(time / 600) * 2); + const x = Math.round(a.at[0] - w / 2), y = a.at[1] - h + bob; + ctx.save(); + const halo = ctx.createRadialGradient(a.at[0], y + h * 0.45, 0, a.at[0], y + h * 0.45, h * 0.7); + halo.addColorStop(0, 'rgba(180, 230, 255, 0.28)'); + halo.addColorStop(1, 'rgba(180, 230, 255, 0)'); + ctx.globalCompositeOperation = 'lighter'; + ctx.fillStyle = halo; + ctx.fillRect(a.at[0] - h, y - h * 0.3, h * 2, h * 1.6); + ctx.globalCompositeOperation = 'source-over'; + ctx.globalAlpha = 0.82 + 0.08 * Math.sin(time / 380); + ctx.drawImage(img, x, y, w, h); + ctx.restore(); + } +} + // Le scene buie (`shade`: il velo scuro sopra tutto, Andrea compreso) e le fiammelle (`lights`: candele accese). // Ogni fiamma ha due disegni che si alternano a caso, e intorno un alone caldo che pulsa piano. const FLAME_STEP = 140; diff --git a/src/story/story.js b/src/story/story.js index 4985baf..06955b0 100644 --- a/src/story/story.js +++ b/src/story/story.js @@ -99,7 +99,7 @@ export const story = { andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' }, vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' }, vecchietto: { name: 'Vecchietto', color: '#9fe3ff', sprite: 'assets/characters/vecchietto.json' }, - castello: { name: 'Il Castello', color: '#c8f0a0' }, + castello: { name: 'Lo spirito del Trebbio', color: '#c8f0ff' }, leone: { name: 'Il leone', color: '#f0a070' }, san_pietro: { name: 'San Pietro', color: '#e8e0c8' }, tosca: { name: 'La Tosca', color: '#ffb0d0' }, @@ -1736,7 +1736,9 @@ export const story = { walk: [[86, 308], [300, 306], [356, 308], [470, 312], [500, 324], [486, 360], [88, 360], [80, 330]], depth: [[306, 0.75], [360, 1.05]], entry: [110, 345], - speakers: { castello: [389, 22] }, + // Lo spirito del castello (disegnato da Codex, idea di Andrea del 24 ottobre): compare quando parla. + speakers: { castello: [446, 178] }, + apparitions: [{ who: 'castello', src: 'assets/characters/spirito-trebbio.png', at: [446, 304], height: 120 }], enter: [{ if: '!trebbio_visto', then: [{ set: 'trebbio_visto' }, { say: 'Il Trebbio. Quassù anche i pensieri vanno più piano.' }] }], hotspots: [ { diff --git a/tests/render.test.mjs b/tests/render.test.mjs index e489954..825f17a 100644 --- a/tests/render.test.mjs +++ b/tests/render.test.mjs @@ -65,3 +65,13 @@ test('le candele tremolano: due disegni che si alternano a caso, non tutte insie for (const [id, sc] of Object.entries(story.scenes)) for (const { at: [x, y] } of sc.lights ?? []) assert.ok(x >= 0 && x < 640 && y >= 4 && y < 360, `${id}: candela fuori scena`); }); + +test('le apparizioni (lo spirito del Trebbio) hanno la loro immagine e parlano con una voce del gioco', async () => { + const { story } = await import('../src/story/story.js'); + const { existsSync } = await import('node:fs'); + for (const [id, sc] of Object.entries(story.scenes)) for (const a of sc.apparitions ?? []) { + assert.ok(existsSync(new URL('../' + a.src, import.meta.url)), `${id}: manca ${a.src}`); + assert.ok(story.characters[a.who], `${id}: ${a.who} non è un personaggio`); + assert.ok(sc.speakers?.[a.who], `${id}: il fumetto di ${a.who} non ha un posto`); + } +});