Campomigliaio collegato (strada, circolino, ponte), il Poggio verso la Fortezza; Ringraziamenti coi ritratti e il carosello

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
2026-10-24 19:43:28 +02:00
co-authored by Claude Opus 5.5
parent 3e99fb094c
commit b76e2ad1b8
55 changed files with 390 additions and 76 deletions
+11 -4
View File
@@ -5,7 +5,7 @@ import { chromium } from 'playwright';
import { spawn } from 'node:child_process';
import { mkdirSync, readFileSync } from 'node:fs';
import assert from 'node:assert/strict';
import { solution } from '../src/story/solution.js';
import { solution, route } from '../src/story/solution.js';
const OUT = process.env.OUT ?? '/tmp/regolo-shots', PORT = 8000 + Math.floor(Math.random() * 900);
mkdirSync(OUT, { recursive: true });
@@ -64,9 +64,16 @@ try {
await page.screenshot({ path: `${OUT}/01-titolo.png` });
// I crediti dal titolo: si aprono, mostrano i nomi, Esc li chiude.
await page.click('#title .open-credits');
assert.match(await page.textContent('#credits'), /Crediti\s*Al Lele/, 'la dedica al Lele viene per prima');
assert.match(await page.textContent('#credits'), /Yuri Baldi/);
assert.match(await page.textContent('#credits'), /Ringraziamenti\s*Al Lele/, 'la dedica al Lele viene per prima');
assert.match(await page.textContent('#credits'), /Baldoria \(Yuri Baldi\)/);
assert.match(await page.textContent('#credits'), /Francesco Bacci/);
// I nomi col ritratto: si tocca il nome, compare il volto; un altro tocco lo chiude.
await page.click('#credits .credit-face >> text=Mariano');
assert.ok(await page.isVisible('#face img'));
await page.waitForFunction(() => document.querySelector('#face img').naturalWidth > 0, null, { timeout: 5000 });
await page.screenshot({ path: `${OUT}/01d-ritratto.png` });
await page.click('#face');
assert.ok(await page.isHidden('#face'));
const onTop = await page.evaluate(() => { const r = document.getElementById('close-credits').getBoundingClientRect();
return document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)?.id === 'close-credits'; });
assert.ok(onTop, 'i crediti stanno sopra il titolo, non dietro');
@@ -214,7 +221,7 @@ try {
// Una partita a metà riprende da dove era rimasta.
await page.click('#restart');
await settle(page);
for (const exit of ['verso_villa_adami', 'su_villa_adami', 'salita', 'su_fortezza', 'sentiero_trebbio', 'ai_cipressi', 'al_trebbio']) {
for (const [, exit] of route('piazza', 'trebbio')) {
await clickScene(page, await hotspotCenter(page, exit));
await settle(page);
}
+8 -5
View File
@@ -77,14 +77,17 @@ test('la storia è coerente: scene, oggetti, dialoghi e punti calpestabili esist
}
});
const nameOf = n => typeof n === 'string' ? n : n.name;
test('i crediti: ogni voce ha un ruolo e almeno un nome', () => {
assert.ok(story.credits.length > 0);
for (const c of story.credits) {
assert.ok(c.role?.trim(), 'ruolo vuoto');
assert.ok(c.names?.length && c.names.every(n => n.trim()), `nomi vuoti in ${c.role}`);
assert.ok(c.names?.length && c.names.every(n => nameOf(n).trim()), `nomi vuoti in ${c.role}`);
for (const n of c.names) if (n.face) assert.ok(existsSync(new URL('../' + n.face, import.meta.url)), `manca il ritratto ${n.face}`);
}
const all = story.credits.flatMap(c => c.names);
for (const name of ['Yuri Baldi', 'Francesco Bacci']) assert.ok(all.includes(name), `manca ${name}`);
const all = story.credits.flatMap(c => c.names.map(nameOf));
for (const name of ['Baldoria (Yuri Baldi)', 'l\'Arcu (Marco Arcuri)', 'Francesco Bacci']) assert.ok(all.includes(name), `manca ${name}`);
});
test('i crediti: i nomi di ogni voce escono in ordine casuale, sempre tutti', () => {
@@ -95,8 +98,8 @@ test('i crediti: i nomi di ogni voce escono in ordine casuale, sempre tutti', ()
const random = () => ((x = (x * 16807) % 2147483647) / 2147483647);
const out = shuffledCredits(story.credits, random);
assert.deepEqual(out.map(c => c.role), story.credits.map(c => c.role), 'le voci restano nel loro ordine');
out.forEach((c, i) => assert.deepEqual([...c.names].sort(), [...story.credits[i].names].sort()));
orders.add(out.find(c => c.role === idee).names.join('|'));
out.forEach((c, i) => assert.deepEqual(c.names.map(nameOf).sort(), story.credits[i].names.map(nameOf).sort()));
orders.add(out.find(c => c.role === idee).names.map(nameOf).join('|'));
}
assert.ok(orders.size > 3, `${idee}: l'ordine non cambia`);
});