Musica, vecchietti con sprite, nuove scene del paese e generatore degli sfondi

Lavoro della sessione precedente: colonna sonora (titolo, paese, ricordi) con muto e volume,
vecchietta e vecchietto sulla panchina, scene di Pieve, oratorio, Via de' Medici, Villa Adami,
salita, campeggio, Bosco ai Frati e viale dei cipressi, panorama dell'introduzione,
tools/genera.py per gli sfondi dalle foto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UdeKd62opy9PKgpQXtVctF
This commit is contained in:
2026-10-23 12:59:57 +02:00
co-authored by Claude Opus 5.5
parent 5eeb634ceb
commit 33e2598718
45 changed files with 1556 additions and 78 deletions
+18 -2
View File
@@ -47,6 +47,8 @@ test('la storia è coerente: scene, oggetti, dialoghi e punti calpestabili esist
for (const k of Object.keys(h.actions ?? {})) assert.ok(VERBS.includes(k), `${id}.${h.id}: verbo ${k}`);
for (const k of [...Object.keys(h.with ?? {}), ...Object.keys(h.give ?? {})]) assert.ok(story.items[k], `${id}.${h.id}: oggetto ${k}`);
if (h.exit) {
// Le uscite devono essere facili da prendere, anche col dito sul telefono.
assert.ok(Math.min(h.box[2], h.box[3]) >= 20 && h.box[2] * h.box[3] >= 1500, `${id}.${h.id}: uscita troppo piccola`);
const to = story.scenes[h.exit.scene];
assert.ok(to, `${id}.${h.id}: scena ${h.exit.scene}`);
assert.ok(inside(to.walk, h.exit.at ?? to.entry), `${id}.${h.id}: arrivo fuori dall'area`);
@@ -96,9 +98,23 @@ test('la soluzione si percorre tutta dall\'inizio alla fine', () => {
assert.ok(saves.at(-1).ended, 'la fine non è salvata');
});
test('i fogli dei personaggi hanno tutti i fotogrammi e l\'immagine', () => {
test('i personaggi in scena hanno il foglio e tutte le pose che usano', () => {
const frames = id => JSON.parse(readFileSync(new URL('../' + story.characters[id].sprite, import.meta.url), 'utf8')).frames;
for (const [id, scene] of Object.entries(story.scenes))
for (const p of scene.people ?? []) {
assert.ok(story.characters[p.who]?.sprite, `${id}: ${p.who} senza foglio`);
assert.ok(scene.speakers?.[p.who], `${id}: manca il punto del fumetto di ${p.who}`);
const f = frames(p.who);
for (const pose of [p.idle ?? 'sit', p.blink, ...(p.talk ?? ['talk'])].filter(Boolean)) assert.ok(f[pose], `${p.who}: manca ${pose}`);
}
for (const [where, list] of effectLists())
for (const e of flatten(list))
if (e.pose) assert.ok(frames(e.who ?? 'andrea')[e.pose], `${where}: ${e.who} non ha la posa ${e.pose}`);
});
test('il foglio di Andrea ha tutti i fotogrammi e l\'immagine', () => {
for (const [id, c] of Object.entries(story.characters)) {
if (!c.sprite) continue;
if (!c.sprite || id !== 'andrea') continue; // le pose degli altri dipendono dalla scena (test sopra)
const url = new URL('../' + c.sprite, import.meta.url);
const atlas = JSON.parse(readFileSync(url, 'utf8'));
assert.ok(existsSync(new URL(atlas.image, url)), `${id}: manca ${atlas.image}`);