Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
104 lines
5.4 KiB
JavaScript
104 lines
5.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { Game } from '../src/engine/game.js';
|
|
import { story } from '../src/story/story.js';
|
|
import { settle } from './helpers.mjs';
|
|
import { Soundscape } from '../src/engine/soundscape.js';
|
|
import { soundscapes, soundscapeFor } from '../src/story/soundscapes.js';
|
|
|
|
function finale() {
|
|
const g = new Game(story, { state: { scene: 'fortezza', x: 462, y: 310, facing: 1, inventory: ['torcia'],
|
|
flags: { indizio_regolo: true, casetta_aperta: true }, used: {}, ended: false } });
|
|
g.act('use', 'fessura', 'torcia'); settle(g);
|
|
return g;
|
|
}
|
|
|
|
test('la torcia aspetta la ricerca, richiede una sosta e non scopre gli occhi prima del quadro', () => {
|
|
const g = finale();
|
|
assert.equal(g.closeup.step, 'search');
|
|
assert.equal(g.act('look', 'bastione'), false);
|
|
assert.equal(g.walkTo([100, 300]), false);
|
|
assert.equal(g.travel('piazza'), false);
|
|
const eyes = story.closeups.fessura.targets[2].at;
|
|
g.closeupAim(eyes); g.update(2);
|
|
assert.equal(g.closeup.found, 0);
|
|
g.closeupAim(story.closeups.fessura.targets[0].at); g.update(0.3);
|
|
g.closeupAim(eyes); g.update(0.2);
|
|
g.closeupAim(story.closeups.fessura.targets[0].at); g.update(0.3);
|
|
assert.equal(g.closeup.step, 'search', 'la sosta riparte quando si sposta il fascio');
|
|
g.update(0.2);
|
|
assert.equal(g.closeup.step, 'reveal');
|
|
assert.equal(g.speech, null, 'la pietra non rivela il testo del quadro');
|
|
assert.equal(g.state.flags.palio_trovato, undefined);
|
|
const lines = settle(g, { explore: true }).map(s => s.text);
|
|
assert.ok(lines.indexOf('È il palio! Il quadro dei ragazzi delle medie!') < lines.indexOf('E quelli... sono due occhi gialli!'));
|
|
assert.ok(g.checkpoint.ended && g.checkpoint.flags.palio_trovato);
|
|
});
|
|
|
|
test('uscire dalla fessura o ricaricare durante la ricerca non finisce la partita e permette di riprovare', () => {
|
|
const g = finale(), saved = g.savable();
|
|
g.closeupAim(story.closeups.fessura.targets[0].at); g.update(1);
|
|
assert.deepEqual(g.savable(), saved);
|
|
assert.ok(g.closeupClose()); settle(g);
|
|
assert.ok(!g.state.ended && !g.state.flags.palio_trovato);
|
|
assert.ok(g.state.inventory.includes('torcia'));
|
|
const reload = new Game(story, { state: g.savable() });
|
|
reload.act('use', 'fessura', 'torcia'); settle(reload);
|
|
assert.equal(reload.closeup.found, 0);
|
|
settle(reload, { explore: true });
|
|
assert.ok(reload.state.ended);
|
|
});
|
|
|
|
test('aprire un primo piano ferma gli effetti successivi fino alla sua chiusura', () => {
|
|
const g = new Game(story);
|
|
g.queue = [{ closeup: 'libro' }, { set: 'dopo_libro' }];
|
|
g.update(0);
|
|
assert.equal(g.state.flags.dopo_libro, undefined);
|
|
g.closeupClose(); settle(g);
|
|
assert.equal(g.state.flags.dopo_libro, true);
|
|
});
|
|
|
|
function audioContext() {
|
|
const param = () => ({ value: 0, setValueAtTime(v) { this.value = v; }, linearRampToValueAtTime(v) { this.value = v; },
|
|
exponentialRampToValueAtTime(v) { this.value = v; }, setTargetAtTime(v) { this.value = v; }, cancelScheduledValues() {} });
|
|
const node = () => ({ gain: param(), frequency: param(), Q: param(), pan: param(), connect() {}, disconnect() {}, start() {}, stop() {} });
|
|
return { currentTime: 0, sampleRate: 100, destination: {}, state: 'running',
|
|
createGain: node, createBufferSource: node, createBiquadFilter: node, createStereoPanner: node, createOscillator: node,
|
|
createBuffer: (_, length) => ({ getChannelData: () => new Float32Array(length) }) };
|
|
}
|
|
|
|
test('suoni: muto e pausa fermano anche gli eventi già avviati, poi il fondo riparte', () => {
|
|
const s = new Soundscape({ createContext: audioContext, random: () => 0.3 });
|
|
s.unlock(); s.configure({ active: true }); s.update('acqua', 'pomeriggio', 0.1); s.play('page');
|
|
assert.ok(s.nodes.size > 1);
|
|
s.configure({ muted: true });
|
|
assert.equal(s.nodes.size, 0); assert.equal(s.master.gain.value, 0);
|
|
s.play('bell'); assert.equal(s.nodes.size, 0);
|
|
s.configure({ muted: false }); s.update('acqua', 'pomeriggio', 0.1);
|
|
assert.ok(s.bed);
|
|
s.configure({ active: false }); assert.equal(s.nodes.size, 0);
|
|
s.configure({ active: true, volume: 0 }); s.play('page'); assert.equal(s.nodes.size, 0);
|
|
});
|
|
|
|
test('suoni: profili distinti, niente uccelli di sera, nessun accumulo ai cambi di luogo', () => {
|
|
assert.equal(soundscapeFor('fortezza', 'fessura'), 'fessura');
|
|
assert.notEqual(soundscapeFor('campomigliaio_ponte'), soundscapeFor('pieve_interno'));
|
|
assert.equal(soundscapeFor('circolo_4_maggio'), 'biliardo', 'nella sala si sentono gli altri tavoli');
|
|
for (const def of Object.values(soundscapes)) assert.ok(!def.night.includes('bird'));
|
|
const s = new Soundscape({ createContext: audioContext, random: () => 0.3 });
|
|
s.unlock(); s.configure({ active: true });
|
|
for (let i = 0; i < 30; i++) {
|
|
s.update(i % 2 ? 'acqua' : 'bosco', 'sera', 0.1);
|
|
assert.equal(s.nodes.size, 1);
|
|
}
|
|
const events = []; s.play = kind => events.push(kind);
|
|
s.update('paese', 'sera', 0.1); s.update('paese', 'sera', 10);
|
|
assert.deepEqual(events, ['cricket']);
|
|
s.update('pieve', 'sera', 0.1); assert.equal(s.nodes.size, 0);
|
|
});
|
|
|
|
test('Web Audio mancante non interrompe la partita', () => {
|
|
const s = new Soundscape({ createContext: () => { throw new Error('non disponibile'); } });
|
|
assert.doesNotThrow(() => { s.unlock(); s.configure({ active: true }); s.update('paese', 'sera', 1); s.play('page'); });
|
|
});
|