From 26da5030a658d7efc5b9770bf7693e9d74a9973b Mon Sep 17 00:00:00 2001 From: Andrea Manzi Date: Sun, 1 Nov 2026 18:19:27 +0100 Subject: [PATCH] Il Regolo del futuro con la musica di Andrea (flipper-futuro.mp3, la musica cambia con la tavola: tableMusic) al posto del battito sintetico Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T --- src/engine/flipper.js | 3 +-- src/engine/music.js | 6 ++++-- src/engine/soundscape.js | 3 +-- src/main.js | 2 +- src/story/story.js | 3 ++- tests/flipper.test.mjs | 2 +- tests/music.test.mjs | 7 +++++-- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/engine/flipper.js b/src/engine/flipper.js index 2ccdb0c..7d7136a 100644 --- a/src/engine/flipper.js +++ b/src/engine/flipper.js @@ -109,7 +109,7 @@ export function futureLayout() { } function openFuture(c) { Object.assign(c, futureLayout()); c.table = 'future'; c.resets = {}; c.fx = []; - c.future = { hits: { orbita: 0, ponte: 0, spira: 0 }, beatAt: c.sim }; + c.future = { hits: { orbita: 0, ponte: 0, spira: 0 } }; // La trasformazione parcheggia le tre palline al sicuro, poi le serve una alla volta. activeBalls(c).forEach((b, i) => Object.assign(b, { x: i === 1 ? 411 : 229, y: 220 - i * 32, vx: 0, vy: 160, ramp: null, contacts: [], trail: [], releaseAt: c.transform.until + i * 0.55, releasedFrom: i === 1 ? 'spira' : 'orbita' })); @@ -243,7 +243,6 @@ function step(c) { } if (c.table === 'future') { c.regolo.x = 320 + Math.sin(c.sim * 1.6) * 24; - if (c.sim >= c.future.beatAt) { c.future.beatAt = c.sim + 0.5; c.events.push({ kind: `flipper-futuro-beat-${Math.floor(c.sim * 2) % 4}`, power: 0.4 }); } } for (const r of c.ramps) r.lit = Math.max(0, r.lit - STEP); if (c.lastRamp && c.sim - c.lastRamp.at > RAMP.combo) c.lastRamp = null; diff --git a/src/engine/music.js b/src/engine/music.js index 3bca7c7..5a1976a 100644 --- a/src/engine/music.js +++ b/src/engine/music.js @@ -13,12 +13,14 @@ import { versioned } from '../version.js'; // suona il suo, più forte quanto più Andrea (`at`) le è vicino (mix). Le console possono stare anche fuori dal quadro: // è la musica della piazza accanto, che arriva da lontano. // `ok(cond)` dice se una condizione della storia vale adesso (game.js, check). -export function trackFor(story, { screen, scene, phase, closeup, at, ok = () => false }) { +// `table`: la tavola del primo piano, se ne ha più d'una (il flipper: 'classic' o 'future'), che può avere la sua musica. +export function trackFor(story, { screen, scene, phase, closeup, table, at, ok = () => false }) { const m = story.music ?? {}; if (screen === 'splash') return null; if (screen === 'title' || screen === 'intro') return m.title ?? null; if (screen === 'ending') return m.ending ?? m.game ?? null; - if (closeup && story.closeups?.[closeup]?.music) return story.closeups[closeup].music; + const cu = closeup && story.closeups?.[closeup]; + if (cu?.music) return cu.tableMusic?.[table] ?? cu.music; const sc = story.scenes[scene]; const stages = (sc?.stages ?? []).filter(st => ok(st.when)); if (stages.length) return mix(stages, at ?? sc.entry ?? [320, 300]); diff --git a/src/engine/soundscape.js b/src/engine/soundscape.js index 90d129f..98c57bd 100644 --- a/src/engine/soundscape.js +++ b/src/engine/soundscape.js @@ -175,9 +175,8 @@ export class Soundscape { else if (kind === 'flipper-jackpot') { for (const [i, f] of [523, 659, 784, 1046, 1318, 1568].entries()) this.tone(f, f, 0.14, 0.06, i * 0.07, pan); for (const [i, f] of [1046, 1318, 1568].entries()) this.tone(f, f * 1.01, 0.7, 0.05, 0.5 + i * 0.04, pan); } else if (kind === 'flipper-super-open') { this.noiseSound(0.45, 1600, 0.04, { pan }); this.tone(180, 1200, 0.65, 0.06, 0, pan); for (const [i, f] of [659, 880, 1318].entries()) this.tone(f, f, 0.18, 0.05, 0.5 + i * 0.12, pan); } else if (kind === 'flipper-super') { for (const [i, f] of [392, 523, 659, 784, 1046, 1318, 1568, 2093].entries()) this.tone(f, f * 1.005, 0.24, 0.055, i * 0.08, pan); for (const [i, f] of [523, 784, 1046].entries()) this.tone(f, f, 0.9, 0.045, 0.65 + i * 0.04, pan); } - // Timbri originali della tavola nascosta: botto grave, scintille, arpeggio pulsante in simulazione. + // Timbri originali della tavola nascosta: botto grave e scintille (la musica è flipper-futuro.mp3, di Andrea). else if (kind === 'flipper-esplosione') { this.noiseSound(1.1, 700, 0.14, { pan }); this.tone(130, 32, 1.2, 0.13, 0, pan); this.tone(90, 1800, 1.6, 0.06, 0.7, pan); } - else if (kind.startsWith('flipper-futuro-beat-')) { const f = [196, 294, 247, 370][Number(kind.at(-1))]; this.tone(85, 42, 0.12, 0.035, 0, pan); this.tone(f, f * 1.005, 0.18, 0.025, 0, pan); this.tone(f * 2, f * 2, 0.1, 0.018, 0.25, pan); } else if (kind === 'flipper-futuro-giro') { this.tone(390, 1560, 0.38, 0.065, 0, pan); this.tone(1174, 1174, 0.25, 0.04, 0.25, pan); } else if (kind === 'flipper-futuro-finale') { this.noiseSound(0.7, 1600, 0.07, { pan }); for (const [i, f] of [196, 294, 494, 740, 988, 1480].entries()) this.tone(f, f, 0.5, 0.055, i * 0.12, pan); } else if (kind === 'flipper-rampa') { this.noiseSound(0.25, 2400, 0.04, { pan }); this.tone(300, 900, 0.3, 0.04, 0, pan); } diff --git a/src/main.js b/src/main.js index d23f5dd..283c0d6 100644 --- a/src/main.js +++ b/src/main.js @@ -1194,7 +1194,7 @@ function frame(now) { const searching = game?.closeup?.id === 'fessura'; music.duck = searching ? game.closeup.found >= 2 ? 0.05 : 0.25 : game?.state.flags.occhi_regolo ? 0.12 : voices.playing ? 0.45 : 1; music.update(trackFor(story, { screen, scene: game?.state.scene, phase: game?.phase(), closeup: game?.closeup?.id, - at: game && [game.state.x, game.state.y], ok: cond => !!game && check(cond, game.state) }), dt); + table: game?.closeup?.table, at: game && [game.state.x, game.state.y], ok: cond => !!game && check(cond, game.state) }), 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(); ambience.configure({ active: screen === 'game' && running && !document.hidden && $('credits').hidden, muted: music.muted, duck: voices.playing ? 0.4 : 1 }); diff --git a/src/story/story.js b/src/story/story.js index 9f3eba8..40fdca9 100644 --- a/src/story/story.js +++ b/src/story/story.js @@ -363,7 +363,8 @@ export const story = { // Il flipper del River (Andrea, 31 ottobre: «una tavola bellissima, con San Piero dentro»): la tavola di Codex, // fisica e parti meccaniche in flipper.js. [DA ANDREA: le battute sono provvisorie] flipper: { - type: 'flipper', music: 'assets/audio/flipper.mp3', // di Andrea (Suno, 1 novembre) + // Le musiche di Andrea (Suno, 1 novembre): la classica, e quella del Regolo del futuro dopo la SUPER SPECIAL. + type: 'flipper', music: 'assets/audio/flipper.mp3', tableMusic: { future: 'assets/audio/flipper-futuro.mp3' }, picture: 'assets/closeups/river-flipper.png', backglass: 'assets/closeups/river-flipper-tabellone.png', // il fondale luminoso del tabellone (Codex: Trebbio, Fortezza, Regolo, fuochi) names: { player: 'Andrea', cpu: 'il flipper' }, diff --git a/tests/flipper.test.mjs b/tests/flipper.test.mjs index 44feb19..c325e3c 100644 --- a/tests/flipper.test.mjs +++ b/tests/flipper.test.mjs @@ -562,7 +562,7 @@ test('il ball save nel futuro salva una sola volta, conserva campo e sigilli, po test('i tre percorsi del futuro sono raggiungibili con il colpo fisico della paletta', () => { for (const [id, x, y] of [['orbita', 288, 300], ['ponte', 290, 306], ['spira', 308, 320]]) { const c = m.newFlipper(9); - Object.assign(c, m.futureLayout(), { table: 'future', future: { hits: { orbita: 0, ponte: 0, spira: 0 }, beatAt: 0 } }); + Object.assign(c, m.futureLayout(), { table: 'future', future: { hits: { orbita: 0, ponte: 0, spira: 0 } } }); m.launch(c, 0.8); c.saveUntil = 0; put(c, { x, y, vx: 0, vy: 180, inPlay: true }); m.hold(c, 'left', 'test', true); let entered = null; diff --git a/tests/music.test.mjs b/tests/music.test.mjs index a51433a..1d6a8f4 100644 --- a/tests/music.test.mjs +++ b/tests/music.test.mjs @@ -24,10 +24,13 @@ test('il brano giusto per titolo, fine e scena', () => { assert.equal(trackFor({ ...s, scenes: { ...s.scenes, i: { indoor: true } } }, { screen: 'game', scene: 'i', phase: sera }), 'g.mp3'); }); -test('il flipper del River ha la sua musica, e il file c\'è', () => { +test('il flipper del River ha la sua musica, e un\'altra la tavola del futuro', () => { const track = trackFor(story, { screen: 'game', scene: 'river', closeup: 'flipper' }); assert.equal(track, 'assets/audio/flipper.mp3'); - assert.ok(existsSync(track)); + assert.equal(trackFor(story, { screen: 'game', scene: 'river', closeup: 'flipper', table: 'classic' }), track); + const future = trackFor(story, { screen: 'game', scene: 'river', closeup: 'flipper', table: 'future' }); + assert.equal(future, 'assets/audio/flipper-futuro.mp3', 'il Regolo del futuro ha la sua musica'); + for (const f of [track, future]) assert.ok(existsSync(f), f); }); test('volume e muto', () => {