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:
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user