Per ora si corre solo per Campomigliaio: gli altri rioni si vedono chiusi

Piazza, Pieve e Cardetole restano nella scelta, col lucchetto e «prossimamente» (locked in story.rioni), per non
moltiplicare battute e voci; in docs/STATO.md cosa fare quando si sbloccano.

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 15:32:53 +02:00
co-authored by Claude Opus 5.5
parent 94a5479645
commit 80907f2fa0
6 changed files with 21 additions and 7 deletions
+5
View File
@@ -107,6 +107,11 @@ I prompt fissi (stile, scene, foglio sprite) sono in `prompts/STILE.md`, escluso
(`story.rioni`: Piazza rossi, Pieve bianchi, Cardetole verdi, Campomigliaio blu); resta nella partita come flag (`story.rioni`: Piazza rossi, Pieve bianchi, Cardetole verdi, Campomigliaio blu); resta nella partita come flag
`rione:<id>`. Cambiano la riga dell'introduzione e le battute su Pieve, Cardetole, Campomigliaio e la colonna. `rione:<id>`. Cambiano la riga dell'introduzione e le battute su Pieve, Cardetole, Campomigliaio e la colonna.
Le parti future col rione (l'Arcu, il capitano) vanno scritte per ogni scelta. Le parti future col rione (l'Arcu, il capitano) vanno scritte per ogni scelta.
**Per ora si sceglie solo Campomigliaio** (Andrea, 24 ottobre, per non moltiplicare battute e voci): Piazza, Pieve e
Cardetole si vedono chiusi (`locked: true` in `story.rioni`). **Quando si decide di sbloccarli**: togliere `locked`,
rileggere le battute per rione già scritte (introduzione, colonna, Pieve, strada per Cardetole e per Campomigliaio:
cercare `rione:` in `story.js`), scrivere per ogni rione le parti nuove nate nel frattempo (l'Arcu, il capitano),
`make voci`, e aggiornare il test «per ora si corre solo per Campomigliaio».
- **Audio sul telefono**: passando a un'altra app (pagina nascosta) musica, voce e musichetta si fermano subito e la - **Audio sul telefono**: passando a un'altra app (pagina nascosta) musica, voce e musichetta si fermano subito e la
partita va in pausa; la musica riparte con Riprendi. partita va in pausa; la musica riparte con Riprendi.
- **Pronuncia** (`tools/pronuncia.json`): le parole che XTTS legge male, scritte come vanno lette; vale solo per la voce. - **Pronuncia** (`tools/pronuncia.json`): le parole che XTTS legge male, scritte come vanno lette; vale solo per la voce.
+3 -1
View File
@@ -67,7 +67,9 @@ $('rione-buttons').replaceChildren(...Object.entries(story.rioni ?? {}).map(([id
b.style.setProperty('--rione', r.color); b.style.setProperty('--rione', r.color);
b.innerHTML = '<span class="rione-name"></span><span class="rione-colors"></span>'; b.innerHTML = '<span class="rione-name"></span><span class="rione-colors"></span>';
b.querySelector('.rione-name').textContent = r.name; b.querySelector('.rione-name').textContent = r.name;
b.querySelector('.rione-colors').textContent = r.colors; b.querySelector('.rione-colors').textContent = r.locked ? 'prossimamente' : r.colors;
// Un rione chiuso si vede ma non si sceglie (per ora si corre solo per Campomigliaio).
if (r.locked) { b.disabled = true; b.classList.add('locked'); b.title = 'Prossimamente'; }
b.addEventListener('click', () => { rione = id; $('rione').hidden = true; showIntro(); }); b.addEventListener('click', () => { rione = id; $('rione').hidden = true; showIntro(); });
return b; return b;
})); }));
+5 -3
View File
@@ -38,10 +38,12 @@ export const story = {
start: { scene: 'piazza', at: [250, 320] }, start: { scene: 'piazza', at: [250, 320] },
// I quattro rioni del Palio: dopo «Nuova partita» si sceglie per chi corre Andrea, e la partita lo ricorda col // I quattro rioni del Palio: dopo «Nuova partita» si sceglie per chi corre Andrea, e la partita lo ricorda col
// flag `rione:<id>`. Le battute che parlano del rione di Andrea lo controllano con `if`. // flag `rione:<id>`. Le battute che parlano del rione di Andrea lo controllano con `if`.
// Per ora si corre solo per Campomigliaio (decisione di Andrea, 24 ottobre: ogni rione vuol dire battute e voci
// in più); gli altri si vedono chiusi (`locked`) e si sbloccano togliendo la riga.
rioni: { rioni: {
piazza: { name: 'Piazza', colors: 'i rossi', color: '#d0453a' }, piazza: { name: 'Piazza', colors: 'i rossi', color: '#d0453a', locked: true },
pieve: { name: 'Pieve', colors: 'i bianchi', color: '#f2eee2' }, pieve: { name: 'Pieve', colors: 'i bianchi', color: '#f2eee2', locked: true },
cardetole: { name: 'Cardetole', colors: 'i verdi', color: '#3fa34d' }, cardetole: { name: 'Cardetole', colors: 'i verdi', color: '#3fa34d', locked: true },
campomigliaio: { name: 'Campomigliaio', colors: 'i blu', color: '#2f6fd0' }, campomigliaio: { name: 'Campomigliaio', colors: 'i blu', color: '#2f6fd0' },
}, },
// Le righe dell'introduzione, sopra il panorama del paese, prima della prima scena. // Le righe dell'introduzione, sopra il panorama del paese, prima della prima scena.
+3
View File
@@ -222,3 +222,6 @@ body.full #sound { position: fixed; z-index: 13; top: max(8px, env(safe-area-ins
#rione-buttons button:hover, #rione-buttons button:focus-visible { background: color-mix(in srgb, var(--rione) 30%, #1b1420); } #rione-buttons button:hover, #rione-buttons button:focus-visible { background: color-mix(in srgb, var(--rione) 30%, #1b1420); }
#rione-buttons .rione-colors { font-size: .7em; color: var(--rione); } #rione-buttons .rione-colors { font-size: .7em; color: var(--rione); }
#rione .hint { color: var(--dim); font-size: clamp(8px, 1.2vw, 11px); text-align: center; } #rione .hint { color: var(--dim); font-size: clamp(8px, 1.2vw, 11px); text-align: center; }
#rione-buttons button.locked { cursor: not-allowed; opacity: .5; filter: grayscale(.6); }
#rione-buttons button.locked .rione-name::before { content: '🔒 '; font-family: sans-serif; font-size: .8em; }
#rione-buttons button.locked .rione-colors { color: var(--dim); }
+4 -3
View File
@@ -81,11 +81,12 @@ try {
await page.waitForSelector('#rione:not([hidden])'); await page.waitForSelector('#rione:not([hidden])');
assert.equal(await page.locator('#rione-buttons button').count(), 4); assert.equal(await page.locator('#rione-buttons button').count(), 4);
await page.screenshot({ path: `${OUT}/01a-rione.png` }); await page.screenshot({ path: `${OUT}/01a-rione.png` });
await page.click('#rione [data-rione="pieve"]'); assert.equal(await page.locator('#rione-buttons button:disabled').count(), 3, 'per ora gli altri tre rioni sono chiusi');
await page.click('#rione [data-rione="campomigliaio"]');
// L'introduzione: una riga per clic, poi comincia la partita. // L'introduzione: una riga per clic, poi comincia la partita.
await page.waitForSelector('#intro:not([hidden])'); await page.waitForSelector('#intro:not([hidden])');
for (let i = 0; i < 2; i++) await page.click('#intro', { position: { x: 40, y: 40 } }); for (let i = 0; i < 2; i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.match(await page.textContent('#intro-text'), /Pieve, i bianchi/, 'la riga del rione scelto'); assert.match(await page.textContent('#intro-text'), /Campomigliaio, i blu/, 'la riga del rione scelto');
await page.screenshot({ path: `${OUT}/01b-intro.png` }); await page.screenshot({ path: `${OUT}/01b-intro.png` });
for (let i = 0; i < 10 && await page.isVisible('#intro'); i++) await page.click('#intro', { position: { x: 40, y: 40 } }); for (let i = 0; i < 10 && await page.isVisible('#intro'); i++) await page.click('#intro', { position: { x: 40, y: 40 } });
assert.ok(await page.isHidden('#intro'), 'l\'introduzione non finisce'); assert.ok(await page.isHidden('#intro'), 'l\'introduzione non finisce');
@@ -95,7 +96,7 @@ try {
assert.match(await page.evaluate(() => voices.audio.src), /assets\/voices\/andrea-[0-9a-f]+\.mp3$/); assert.match(await page.evaluate(() => voices.audio.src), /assets\/voices\/andrea-[0-9a-f]+\.mp3$/);
await page.waitForFunction(() => music.duck === 0.45, null, { timeout: 2000 }); await page.waitForFunction(() => music.duck === 0.45, null, { timeout: 2000 });
await page.screenshot({ path: `${OUT}/02-inizio.png` }); await page.screenshot({ path: `${OUT}/02-inizio.png` });
assert.ok(await page.evaluate(() => game.state.flags['rione:pieve']), 'il rione resta nella partita'); assert.ok(await page.evaluate(() => game.state.flags['rione:campomigliaio']), 'il rione resta nella partita');
const fits = async p => p.evaluate(() => document.querySelector('#panel').getBoundingClientRect().bottom <= innerHeight); const fits = async p => p.evaluate(() => document.querySelector('#panel').getBoundingClientRect().bottom <= innerHeight);
assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo'); assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo');
await settle(page); await settle(page);
+1
View File
@@ -394,6 +394,7 @@ test('ogni posto della mappa ha la sua miniatura (make miniature)', () => {
test('i quattro rioni: ognuno ha la sua riga nell\'introduzione, e le battute sul rione di Andrea lo seguono', () => { test('i quattro rioni: ognuno ha la sua riga nell\'introduzione, e le battute sul rione di Andrea lo seguono', () => {
assert.deepEqual(Object.keys(story.rioni), ['piazza', 'pieve', 'cardetole', 'campomigliaio']); assert.deepEqual(Object.keys(story.rioni), ['piazza', 'pieve', 'cardetole', 'campomigliaio']);
assert.deepEqual(Object.keys(story.rioni).filter(id => !story.rioni[id].locked), ['campomigliaio'], 'per ora si corre solo per Campomigliaio');
const line = story.intro.find(l => typeof l !== 'string'); const line = story.intro.find(l => typeof l !== 'string');
assert.deepEqual(Object.keys(line.rione).sort(), Object.keys(story.rioni).sort(), 'una riga per rione'); assert.deepEqual(Object.keys(line.rione).sort(), Object.keys(story.rioni).sort(), 'una riga per rione');
for (const [id, r] of Object.entries(story.rioni)) assert.ok(introText(line, id).includes(r.colors.replace('i ', '')), `${id}: ${introText(line, id)}`); for (const [id, r] of Object.entries(story.rioni)) assert.ok(introText(line, id).includes(r.colors.replace('i ', '')), `${id}: ${introText(line, id)}`);