Flipper più ricco (Codex): skill shot da 25.000 sulla corsia lampeggiante, spinner RIVER da 500 a giro, salvataggi laterali conquistabili, bonus a fine pallina fino a ×5; spintarella con N, freccia su o SCUOTI (terza scossa: TILT, palette spente e bonus perso); il Regolo del futuro concede 30 secondi per il colpo finale da 500.000; fuochi, fanfare, knocker, ruggito e conto alla rovescia

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECdB3MK67QCXQMfaP2cV
This commit is contained in:
2026-11-02 11:04:26 +01:00
co-authored by Claude Opus 5.5
parent 650d4e9cf8
commit 4b5408597f
8 changed files with 395 additions and 26 deletions
+107
View File
@@ -593,3 +593,110 @@ test('per provare, openSuper fa davvero i due LOCK e apre la SUPER SPECIAL (main
assert.ok(m.openSuper(c));
assert.equal(c.special, 'super'); assert.equal(c.locks.length, 2); assert.equal(c.table, 'classic');
});
test('skill shot: il primo passaggio paga una volta, anche su corsia già accesa; il ball save non lo rinnova', () => {
const c = m.newFlipper(1); m.launch(c, 0.2);
const lane = c.lanes[c.skillShot.lane]; lane.lit = true;
put(c, { x: lane.x, y: 103, vx: 0, vy: 300 });
const score = c.score; m.update(c, m.STEP);
assert.equal(c.score - score, m.SHOW.skill);
assert.equal(c.skillShot.available, false);
c.saveUntil = c.sim + 2;
put(c, { x: 320, y: 348, vx: 0, vy: 300 }); m.update(c, m.STEP);
m.launch(c, 0.2); assert.equal(c.skillShot.active, false);
put(c, { x: lane.x, y: 103, vx: 0, vy: 300 });
const again = c.score; m.update(c, m.STEP); assert.equal(c.score, again);
});
test('le corsie scelte per lo skill shot si raggiungono con la molla a ogni difficoltà', () => {
for (const level of Object.keys(m.LEVELS)) {
const reached = new Set();
for (let power = 0; power <= 100; power++) {
const c = m.newFlipper(42, { level }); m.launch(c, power / 100);
for (let i = 0; i < 900 && c.skillShot.available; i++) m.update(c, m.STEP);
c.lanes.forEach((l, i) => { if (l.lit) reached.add(i); });
}
for (let seed = 0; seed < 6; seed++) assert.ok(reached.has(m.newFlipper(seed).skillShot.lane), `${level}: corsia raggiungibile`);
}
});
test('spinner: un attraversamento reale lo accelera, ogni giro paga e poi si ferma', () => {
const c = m.newFlipper(3); m.launch(c, 0.5); c.skillShot.active = false;
put(c, { x: m.SPINNER.x, y: m.SPINNER.y + 1, vx: 0, vy: -400 }); m.update(c, m.STEP);
assert.ok(c.spinner.speed > 15);
c.ball.waiting = true; const before = c.score; run(c, 4);
assert.ok(c.score > before); assert.equal((c.score - before) % m.SHOW.spinner, 0);
assert.equal(c.spinner.speed, 0);
const stopped = c.score; run(c, 1); assert.equal(c.score, stopped);
});
test('i banchi accendono i kickback, ogni respingente salva una sola uscita laterale', () => {
for (const [bank, side, x] of [['pieve', 'left', 207], ['torre', 'right', 426]]) {
const c = m.newFlipper(25); m.launch(c, 0.8);
for (const t of c.targets.filter(t => t.bank === bank)) {
const dir = bank === 'pieve' ? -1 : 1;
put(c, { x: t.a[0] - dir * 5, y: (t.a[1] + t.b[1]) / 2, vx: dir * 200, vy: 0, contacts: [] }); run(c, 0.02);
}
assert.equal(c.kickback[side], true);
const lives = c.balls;
put(c, { x, y: 274, vx: 0, vy: 400 }); m.update(c, m.STEP);
assert.ok(c.ball.vy < -500); assert.equal(c.kickback[side], false); assert.equal(c.balls, lives);
put(c, { x, y: 274, vx: 0, vy: 400 }); m.update(c, m.STEP); assert.ok(c.ball.vy > 0);
}
});
test('scosse: due avvertimenti, autorepeat ignorato, tilt disattiva palette punti e bonus fino alla prossima vita', () => {
const c = m.newFlipper(17); assert.equal(m.nudge(c), false); m.launch(c, 0.7);
put(c, { x: 320, y: 260, vx: 0, vy: 0 });
m.key(c, 'n', true); m.key(c, 'n', true); assert.equal(c.tilt.warnings, 1); assert.ok(c.ball.vy < 0);
for (let i = 0; i < 2; i++) {
m.key(c, 'n', false); c.sim += 1; m.key(c, 'n', true);
}
assert.equal(c.tilt.active, true); assert.equal(m.hold(c, 'left', 'test', true), false);
c.bonus.lanes = 3; c.bonus.x = 2; c.kickback.left = true;
const score = c.score, lives = c.balls;
const b = c.bumpers[0]; put(c, { x: b.x, y: b.y + b.r + 4, vx: 0, vy: -200, contacts: [] }); m.update(c, m.STEP);
assert.equal(c.score, score);
put(c, { x: 207, y: 274, vx: 0, vy: 400 }); m.update(c, m.STEP); assert.ok(c.ball.vy > 0);
put(c, { x: 320, y: 348, vx: 0, vy: 300 }); m.update(c, m.STEP);
assert.equal(c.score, score); assert.equal(c.bonusPaid.lost, true); assert.equal(c.bonusPaid.total, 0);
assert.equal(c.balls, lives - 1); assert.equal(c.tilt.active, false); assert.equal(c.tilt.warnings, 0);
assert.ok(m.launch(c, 0.7)); assert.ok(m.hold(c, 'left', 'test', true));
});
test('il bonus si paga una sola volta alla perdita della vita, senza consumarlo sui ball save', () => {
const c = m.newFlipper(1); m.launch(c, 0.5);
c.bonus = { lanes: 2, targets: 3, ramps: 1, x: 2 };
const expected = 19000, score = c.score;
assert.equal(m.bonusValue(c), expected);
c.saveUntil = c.sim + 2; put(c, { x: 320, y: 348, vx: 0, vy: 300 }); m.update(c, m.STEP);
assert.equal(c.score, score); assert.equal(m.bonusValue(c), expected);
m.launch(c, 0.5); put(c, { x: 320, y: 348, vx: 0, vy: 300 }); m.update(c, m.STEP);
assert.equal(c.score - score, expected); assert.equal(c.bonusPaid.total, expected); assert.equal(m.bonusValue(c), 0);
});
test('il risveglio dura trenta secondi di simulazione e alla scadenza lascia un giro per sigillo da rifare', () => {
const c = inFuture();
for (const id of ['orbita', 'ponte', 'spira']) { rampShot(c, id); rampShot(c, id); }
assert.ok(c.future.awakeUntil > c.sim);
assert.ok(c.events.some(e => e.kind === 'flipper-risveglio'));
for (const b of m.activeBalls(c)) b.releaseAt = c.sim + 100;
const score = c.score, lives = c.balls;
run(c, 30.1);
assert.equal(c.future.awakeUntil, null);
assert.deepEqual(c.future.hits, { orbita: 1, ponte: 1, spira: 1 });
assert.equal(c.balls, lives); assert.equal(c.score, score);
c.ball.releaseAt = 0;
for (const id of ['orbita', 'ponte', 'spira']) rampShot(c, id);
assert.ok(c.future.awakeUntil > c.sim);
});
test('SCUOTI non intercetta la molla da tastiera o il tasto destro del mouse', () => {
const c = m.newFlipper(10); m.aimScreen(c, [320, 347]);
m.key(c, ' ', true); assert.ok(c.holding); run(c, 0.2); m.key(c, ' ', false);
assert.ok(!m.waiting(c));
m.press(c, 'mouse-right'); assert.ok(c.flippers.right.held.includes('mouse-right'));
assert.equal(c.tilt.warnings, 0); m.release(c, 'mouse-right');
m.press(c, 'touch'); assert.equal(c.tilt.warnings, 1);
assert.equal(c.flippers.left.held.length + c.flippers.right.held.length, 0);
});