Flipper: il tabellone cambia illustrazione con la zona che la pallina attraversa (il Regolo coronato, la Fortezza, la Pieve, la Sieve, il Trebbio, i rioni) e il simbolo della zona sul campo (Codex, flipper-art.js); «TIENI PREMUTO QUI» per la molla solo col dito

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-11-01 21:57:17 +01:00
co-authored by Claude Opus 5.5
parent c68b365df2
commit 7384726091
5 changed files with 341 additions and 2 deletions
+36
View File
@@ -0,0 +1,36 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { newFlipper, futureLayout } from '../src/engine/flipper.js';
import { flipperScene } from '../src/engine/flipper-art.js';
test('il backglass segue i luoghi attraversati, senza modificare la partita', () => {
const c = newFlipper(91);
assert.equal(flipperScene(c), 'regolo');
for (const [x, y, scene] of [[403, 70, 'regolo'], [297, 90, 'rioni'], [220, 168, 'pieve'], [416, 180, 'trebbio'], [290, 135, 'fortezza'], [320, 202, 'regolo'], [320, 290, 'sieve']]) {
Object.assign(c.ball, { waiting: false, x, y });
const before = structuredClone(c);
assert.equal(flipperScene(c), scene, `zona ${x}, ${y}`);
assert.deepEqual(c, before);
}
for (const [id, scene] of [['sinistra', 'fortezza'], ['destra', 'trebbio'], ['super', 'cosmo'], ['orbita', 'cosmo'], ['ponte', 'fortezza'], ['spira', 'spira']]) {
c.ball.ramp = { id };
assert.equal(flipperScene(c), scene);
}
c.regolo.hit = 0.2;
assert.equal(flipperScene(c), 'regolo', 'il colpo al Regolo ha precedenza sulle rampe');
});
test('palline in attesa o trattenute non attivano immagini, il multiball ha una priorità stabile', () => {
const c = newFlipper(92);
c.extraBalls = [{ ...structuredClone(c.ball), x: 210, y: 170, waiting: false, releaseAt: c.sim + 2 }];
assert.equal(flipperScene(c), 'regolo');
Object.assign(c.ball, { waiting: false, ramp: { id: 'destra' } });
Object.assign(c.extraBalls[0], { releaseAt: 0, ramp: { id: 'sinistra' } });
const first = flipperScene(c);
[c.ball, c.extraBalls[0]] = [c.extraBalls[0], c.ball];
assert.equal(flipperScene(c), first);
c.ball = null; c.extraBalls = []; c.step = 'result';
assert.equal(flipperScene(c), 'regolo');
Object.assign(c, futureLayout(), { table: 'future' });
assert.equal(flipperScene(c), 'cosmo');
});
+30
View File
@@ -51,8 +51,38 @@ try {
assert.ok(w > viewport.width * 0.6, `sul telefono la scena del flipper è larga ${w} px`);
}
await page.screenshot({ path: `${out}/${name}-tavola.png` });
// «TIENI PREMUTO QUI» per la molla solo col dito (Andrea): sul computer la tavola non la dice.
assert.equal(await page.evaluate(() => !!game.closeup.touch), mobile, 'la scritta della molla solo sul telefono');
const box = await page.locator('#scene').boundingBox(), P = (x, y) => [box.x + x / 640 * box.width, box.y + y / 360 * box.height];
if (!mobile) await page.screenshot({ path: `${out}/desktop-dettaglio.png`, clip: { x: P(200, 0)[0], y: box.y, width: 248 / 640 * box.width, height: box.height } });
// Le apparizioni seguono zone vere del campo e cambiano effettivamente i pixel del backglass.
if (!mobile) {
await page.evaluate(() => {
window.artState = structuredClone(game.closeup); window.artUpdate = game.update;
game.update = () => {};
});
const pictures = new Set();
for (const [x, y, scene] of [[403, 70, 'regolo'], [290, 135, 'fortezza'], [416, 180, 'trebbio'], [220, 168, 'pieve'], [297, 90, 'rioni'], [320, 290, 'sieve']]) {
const result = await page.evaluate(async ({ x, y }) => {
const { render } = await import('/src/engine/render.js');
const { flipperScene } = await import('/src/engine/flipper-art.js');
const c = game.closeup, ctx = document.querySelector('#scene').getContext('2d');
Object.assign(c.ball, { x, y, waiting: false, releaseAt: 0, ramp: null });
for (let i = 0; i < 4; i++) { c.visual.time += 0.4; render(ctx, game, { time: performance.now() }); }
const pixels = ctx.getImageData(48, 476, 300, 176).data;
let signature = 0;
for (let i = 0; i < pixels.length; i++) signature = (Math.imul(signature, 31) + pixels[i]) | 0;
return { scene: flipperScene(c), signature };
}, { x, y });
assert.equal(result.scene, scene); pictures.add(result.signature);
await page.screenshot({ path: `${out}/desktop-visione-${scene}.png` });
}
assert.equal(pictures.size, 6, 'sei illustrazioni diverse nel display');
await page.evaluate(() => {
Object.assign(game.closeup, window.artState); game.update = window.artUpdate;
delete window.artState; delete window.artUpdate;
});
}
const cdp = mobile ? await page.context().newCDPSession(page) : null;
const touch = (type, points) => cdp.send('Input.dispatchTouchEvent', { type, touchPoints: points.map(([x, y]) => ({ x, y })) });
// La molla: tenendo premuto sulla scena, col mouse e col dito.