La Ghiacciomobile schiantata nel muro giallo di Via Liguri Magelli, coi mattoni rotti e senza targa (ritocco di Codex), fino al tramonto; poi il muro rifatto con la targa. Non è più al circolino. Il motore ha le varianti di sfondo per scena (backdrops) (Andrea)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9KMfjthRUnujCGSzZ
This commit is contained in:
2026-10-29 12:44:00 +01:00
co-authored by Claude Opus 5.5
parent 6a5a80d7af
commit 713e328138
14 changed files with 57 additions and 34 deletions
+3
View File
@@ -162,6 +162,9 @@ export class Game {
}
get scene() { return this.story.scenes[this.state.scene]; }
// Lo sfondo di adesso: la prima variante della scena (`backdrops`, ognuna con la sua condizione `when`) che vale,
// sennò il suo `background`. Così un posto cambia con la storia (la Ghiacciomobile nel muro, poi la targa).
backdrop(scene = this.scene) { return scene.backdrops?.find(b => check(b.when, this.state))?.src ?? scene.background; }
// Occupato: c'è qualcosa in corso che il giocatore non può interrompere. La camminata verso un punto
// o verso un oggetto si può sempre cambiare con un altro clic: la coda che la seguiva viene scartata.
get busy() { return !!this.speech || !!this.closeup || this.reach > 0 || (this.task ? !this.task.interruptible : this.queue.length > 0); }
+6 -6
View File
@@ -131,7 +131,7 @@ export function backgroundOrder(story, first) {
for (const h of story.scenes[queue[i]]?.hotspots ?? [])
if (h.exit && story.scenes[h.exit.scene] && !seen.has(h.exit.scene)) { seen.add(h.exit.scene); queue.push(h.exit.scene); }
for (const id of Object.keys(story.scenes)) if (!seen.has(id)) queue.push(id);
return [...new Set(queue.map(id => story.scenes[id].background).filter(Boolean))];
return [...new Set(queue.flatMap(id => [story.scenes[id].background, ...(story.scenes[id].backdrops ?? []).map(b => b.src)]).filter(Boolean))];
}
// Il personaggio alto PERSON pixel a grandezza 1 (per `size` 1), con i piedi in (x, y); rivolto a sinistra si specchia.
@@ -153,8 +153,8 @@ function drawSprite(ctx, { atlas, img }, frame, x, y, scale, facing, size = 1) {
}
export function render(ctx, game, { hover, spots, debug, time = performance.now() } = {}) {
const scene = game.scene, s = game.state, bg = backgrounds.get(scene.background);
if (!bg && scene.background) loadBackground(scene.background);
const scene = game.scene, s = game.state, backdrop = game.backdrop(), bg = backgrounds.get(backdrop);
if (!bg && backdrop) loadBackground(backdrop);
ctx.imageSmoothingEnabled = false;
ctx.clearRect(0, 0, WIDTH, HEIGHT);
if (bg) ctx.drawImage(bg, 0, 0, WIDTH, HEIGHT);
@@ -395,7 +395,7 @@ export const EFFECTS = {
// Alberi al vento (i cipressi): il pezzo di sfondo si piega tutto insieme dalla base, di frazioni di pixel e con
// due onde lente sovrapposte, così il movimento è morbido e non a scatti.
sway(ctx, e, time, game) {
const bg = backgrounds.get(game.scene.background);
const bg = backgrounds.get(game.backdrop());
if (!bg) return;
const k = bg.width / WIDTH, amount = e.amount ?? 3.2;
for (const [i, [x, y, w, h]] of e.rects.entries()) {
@@ -412,7 +412,7 @@ export const EFFECTS = {
// ognuna a una sua altezza e velocità, uscita da un lato rientra dall'altro. Si vedono solo dove lo sfondo è
// cielo (skyMask): passano dietro alberi, torri e tetti. Le due grandi solo coi cieli larghi (`big`).
clouds(ctx, e, time, game) {
const bg = backgrounds.get(game.scene.background), mask = bg && skyMask(bg);
const bg = backgrounds.get(game.backdrop()), mask = bg && skyMask(bg);
if (!mask) return;
const layer = scratch(), lc = layer.getContext('2d'), [ax, ay, aw, ah] = e.area;
const pool = e.big ? CLOUDS : CLOUDS.slice(2);
@@ -452,7 +452,7 @@ export const EFFECTS = {
// Una bandiera dipinta nello sfondo che sventola: a strisce verticali che salgono e scendono, ferme dalla parte
// dell'asta (`pole`: 'left' o 'right') e sempre più mosse verso il lembo libero.
cloth(ctx, e, time, game) {
const bg = backgrounds.get(game.scene.background);
const bg = backgrounds.get(game.backdrop());
if (!bg) return;
const k = bg.width / WIDTH;
for (const [i, [x, y, w, h]] of e.rects.entries()) {
+1 -1
View File
@@ -1099,7 +1099,7 @@ function frame(now) {
ambience.update(soundscapeFor(game?.state.scene, game?.closeup?.id), game?.phase()?.id, dt);
// A gioco fermo (titolo, pausa, fine) la scena non cambia: un ultimo disegno e poi niente, per la batteria del
// telefono. Si ridisegna solo se nel frattempo arriva lo sfondo che mancava.
const still = game && !running && `${game.state.scene}|${hasBackground(game.scene.background)}`;
const still = game && !running && `${game.state.scene}|${hasBackground(game.backdrop())}`;
if (game && (!still || still !== drawnStill)) {
if (running) game.update(dt);
render(ctx, game, { hover: debug && hoverTarget ? game.hotspot(hoverTarget) : null, spots: running && now < spotsUntil, debug: debugView() });
+16 -15
View File
@@ -2116,23 +2116,11 @@ export const story = {
entry: [320, 330],
// L'Arcu davanti al circolino, accanto alla fontanella (sprite di Codex, 25 ottobre).
speakers: { arcu: [418, 214] },
// La Ghiacciomobile non è più qui: sta nel muro di Via Liguri Magelli (Andrea, 29 ottobre).
people: [
{ who: 'arcu', at: [418, 292], facing: -1, idle: 'stand', talk: ['talk', 'explain'], blink: 'blink', every: 4.2 },
// Oggetti statici: ordinati per profondità come la macchina del tempo, senza voce.
// Con la depth della scena misura circa 160 px; resta sopra il bordo di walk. (Il Betino dell'Arcu sta in
// cima alla salita di Via Liguri Magelli, Andrea 28 ottobre.)
{ who: 'ghiacciomobile', at: [176, 286], size: 0.986, idle: 'idle', talk: [] },
],
hotspots: [
{
id: 'ghiacciomobile', name: 'la Ghiacciomobile', box: [96, 215, 160, 71], at: [240, 308], face: 'up', default: 'look',
// La Corsa nera degli amici (sprite di Codex, 26 ottobre). Parlandole, Andrea ricorda; lei non risponde.
actions: {
look: [{ say: 'La Ghiacciomobile nera. Hai visto cose che noi poveri umani non potremmo neppur lontanamente immaginare.' }],
talk: [{ dialogue: 'ghiacciomobile' }],
use: [{ say: 'Con questo sole, di ghiaccio c\'è rimasto giusto il nome. Vo a piedi.' }],
},
},
{
id: 'circolo', name: 'il circolino', box: [100, 0, 380, 205], at: [300, 290], face: 'up',
actions: {
@@ -2710,6 +2698,9 @@ export const story = {
name: 'Via Liguri Magelli',
map: [76, 97],
background: 'assets/scenes/via-liguri-magelli.png',
// All'inizio la Ghiacciomobile è dentro il muro giallo, coi mattoni rotti (Codex, 29 ottobre); dal tramonto
// (le impronte del Regolo) il muro è rifatto e c'è la sua targa (Andrea, 29 ottobre).
backdrops: [{ when: '!indizio_regolo', src: 'assets/scenes/via-liguri-magelli-muro.png' }],
// La strada gira intorno all'Audi nera parcheggiata davanti al Trappola.
walk: [[0, 360], [0, 320], [140, 262], [226, 198], [272, 198], [300, 242], [370, 246], [400, 252], [580, 318], [640, 330], [640, 360]],
depth: [[198, 0.3], [360, 1.1]],
@@ -2745,11 +2736,21 @@ export const story = {
},
},
{
id: 'targa', name: 'targa della Ghiacciomobile', box: [570, 130, 62, 78], at: [565, 336], face: 'right', default: 'look',
id: 'targa', name: 'targa della Ghiacciomobile', box: [570, 130, 62, 78], at: [565, 336], face: 'right', default: 'look', when: 'indizio_regolo',
actions: {
look: [{ say: '«Qui riposa la Ghiacciomobile, che si è schiantata lì.»' }, { say: 'C\'è anche la fotina. Pareva sempre nuova.' }],
},
},
{
// La Ghiacciomobile nel muro (prima del tramonto). Le battute erano del circolino (Andrea, 26 ottobre);
// [DA ANDREA] quella dello schianto. Parlandole, Andrea ricorda; lei non risponde.
id: 'ghiacciomobile', name: 'la Ghiacciomobile', box: [462, 192, 140, 92], at: [440, 300], face: 'right', default: 'look', when: '!indizio_regolo',
actions: {
look: [{ say: 'La Ghiacciomobile nera, mezza dentro il muro. Hai visto cose che noi poveri umani non potremmo neppur lontanamente immaginare.' }],
talk: [{ dialogue: 'ghiacciomobile' }],
use: [{ say: 'Per tirarla fuori di lì ci vuole altro che le mie braccia.' }],
},
},
{
// La prima battuta è di Andrea (dal circolino, 26 ottobre).
id: 'betino', name: 'il Betino dell\'Arcu', box: [232, 172, 40, 24], at: [250, 206], face: 'up', default: 'look',
@@ -2764,7 +2765,7 @@ export const story = {
},
{
// Il campo del Giannini è laggiù a destra, oltre la siepe (Andrea, 28 ottobre).
id: 'al_giannini', name: 'verso il campo del Giannini', box: [430, 150, 132, 100], at: [520, 306], exit: { scene: 'campo_giannini', at: [60, 250], face: 'right' },
id: 'al_giannini', name: 'verso il campo del Giannini', box: [404, 150, 58, 100], at: [520, 306], exit: { scene: 'campo_giannini', at: [60, 250], face: 'right' },
},
],
},