Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
107 lines
6.9 KiB
JavaScript
107 lines
6.9 KiB
JavaScript
// Il peso della grafica (Andrea, 1 novembre 2026: «prima senza GPU scoprivamo anche gli effetti troppo pesanti»): le
|
|
// altre prove disegnano sulla scheda video e un effetto pesante non le rallenta più. Questa usa sempre swiftshader, il
|
|
// disegno sul processore, come un telefono lento: nel gioco vero, in ogni scena nelle tre fasi e in ogni primo piano,
|
|
// lascia girare il gioco e misura i fotogrammi al secondo e l'arretrato grafico (il disegno chiesto e non ancora fatto).
|
|
// node tests/peso.browser.mjs (PESO_TUTTI=1 stampa ogni misura).
|
|
//
|
|
// Come si misura, e perché così (vedi la memoria «effetti leggeri»): mai leggere i pixel della tela del gioco, perché
|
|
// dopo qualche lettura Chrome la passa al processore e la misura cambia ciò che misura; l'arretrato si cronometra
|
|
// leggendo una tela nuova, abbastanza grande da stare anche lei sulla grafica, che deve aspettare tutto il disegno
|
|
// in coda prima della sua. Tarata il 1° novembre 2026 sul PC di Andrea: una scena normale, a immagini caricate, pochi ms
|
|
// di arretrato e 60 fotogrammi; un ctx.filter blur a tutto schermo 816 ms e 34, quaranta tratti larghi trasparenti 718 ms
|
|
// e 36, trenta veli trasparenti a tutto schermo 374 ms e 45.
|
|
import { chromium } from 'playwright';
|
|
import assert from 'node:assert/strict';
|
|
import { staticServer } from './helpers.mjs';
|
|
import { SWIFTSHADER } from './chromium.mjs';
|
|
|
|
const MAX_BACKLOG = 500, MIN_FPS = 30; // la scena più cara, pieve_interno di pomeriggio, fino a 355 ms sotto carico // ms di arretrato dopo un secondo di gioco; fotogrammi al secondo
|
|
const server = await staticServer();
|
|
let browser;
|
|
const errors = [], slow = [], rows = [];
|
|
try {
|
|
browser = await chromium.launch({ args: SWIFTSHADER });
|
|
const page = await browser.newPage({ viewport: { width: 1280, height: 860 } });
|
|
page.on('pageerror', e => errors.push(e.message));
|
|
await page.goto(`http://127.0.0.1:${server.port}/`);
|
|
await page.click('#splash'); await page.click('#new-game'); await page.click('#rione [data-rione="campomigliaio"]'); await page.click('#skip-intro');
|
|
await page.evaluate(async () => {
|
|
const { settle } = await import('/tests/helpers.mjs');
|
|
settle(game);
|
|
const probe = () => {
|
|
const c = document.createElement('canvas'); c.width = 640; c.height = 360;
|
|
const g = c.getContext('2d'); g.fillRect(0, 0, 9, 9);
|
|
const t = performance.now(); g.getImageData(0, 0, 1, 1); return performance.now() - t;
|
|
};
|
|
window.peso = async () => {
|
|
// Prima si lascia smaltire l'arretrato di prima, compreso il caricamento delle immagini alla prima visita di una
|
|
// scena (che costa una volta sola e non è un effetto): finché la lettura non torna svelta, al più due secondi.
|
|
await new Promise(r => setTimeout(r, 150));
|
|
for (let i = 0; i < 20 && probe() > 30; i++) await new Promise(r => setTimeout(r, 100));
|
|
let n = 0; const t0 = performance.now();
|
|
await new Promise(done => { const tick = () => { n++; if (performance.now() - t0 < 800) requestAnimationFrame(tick); else done(); }; requestAnimationFrame(tick); });
|
|
const backlog = Math.round(probe());
|
|
return { fps: Math.round(n * 1000 / (performance.now() - t0)), backlog };
|
|
};
|
|
});
|
|
// Una misura storta per un attimo di carico del PC (le altre prove girano insieme) si ripete dopo un secondo di
|
|
// respiro: boccia solo se è pesante due volte.
|
|
const measure = async label => {
|
|
let m = await page.evaluate(() => peso());
|
|
const heavy = m => m.backlog > MAX_BACKLOG || m.fps < MIN_FPS;
|
|
if (heavy(m)) { await page.waitForTimeout(1000); m = await page.evaluate(() => peso()); }
|
|
rows.push({ label, ...m });
|
|
if (heavy(m)) slow.push(`${label}: ${m.fps} fotogrammi al secondo, arretrato ${m.backlog} ms`);
|
|
if (process.env.PESO_TUTTI) console.log(`${String(m.fps).padStart(3)} fps ${String(m.backlog).padStart(5)} ms ${label}`);
|
|
};
|
|
// Le scene, nelle tre fasi (le condizioni delle fasi come in ambient.browser.mjs). Prima un giro in tutte, perché la
|
|
// prima visita paga il caricamento delle immagini, che non è un effetto.
|
|
const scenes = await page.evaluate(() => Object.keys(game.story.scenes));
|
|
for (const id of scenes) {
|
|
await page.evaluate(id => { const sc = game.story.scenes[id]; game.state.scene = id; [game.state.x, game.state.y] = sc.entry; }, id);
|
|
await page.waitForTimeout(120);
|
|
}
|
|
for (const [phase, flags] of [['pomeriggio', {}], ['tramonto', { indizio_regolo: true }], ['sera', { casetta_aperta: true }]]) {
|
|
for (const id of scenes) {
|
|
await page.evaluate(({ id, flags }) => {
|
|
const g = game, sc = g.story.scenes[id];
|
|
Object.assign(g.state.flags, flags);
|
|
g.state.scene = id; [g.state.x, g.state.y] = sc.entry; g.enteredAt = g.time;
|
|
}, { id, flags });
|
|
await measure(`${id} (${phase})`);
|
|
await page.evaluate(flags => { for (const k of Object.keys(flags)) delete game.state.flags[k]; }, flags);
|
|
}
|
|
}
|
|
// I primi piani, aperti come li apre la storia; il flipper anche con la pallina in gioco e nel Regolo del futuro.
|
|
const closeups = await page.evaluate(() => Object.keys(game.story.closeups));
|
|
for (const id of closeups) {
|
|
const opened = await page.evaluate(id => {
|
|
game.state.scene = 'piazza';
|
|
try { game.run({ closeup: id }); } catch (e) { return String(e); }
|
|
return true;
|
|
}, id);
|
|
if (opened !== true) { console.log(`${id}: non si apre da solo (${opened})`); continue; }
|
|
await measure(`primo piano ${id}`);
|
|
if (await page.evaluate(() => game.story.closeups[game.closeup.id].type === 'flipper')) {
|
|
await page.evaluate(async () => { const m = await import('/src/engine/flipper.js'); m.launch(game.closeup, 0.8); });
|
|
await measure(`primo piano ${id} (in gioco)`);
|
|
const future = await page.evaluate(async () => {
|
|
const m = await import('/src/engine/flipper.js');
|
|
if (!m.futureLayout) return false;
|
|
Object.assign(game.closeup, m.futureLayout(), { table: 'future', future: { hits: { orbita: 0, ponte: 0, spira: 0 } } });
|
|
return true;
|
|
});
|
|
if (future) await measure(`primo piano ${id} (Regolo del futuro)`);
|
|
}
|
|
await page.evaluate(() => { game.closeup = null; game.queue.length = 0; game.speech = null; });
|
|
}
|
|
const worst = [...rows].sort((a, b) => b.backlog - a.backlog).slice(0, 4).map(r => `${r.label} ${r.backlog} ms`).join(', ');
|
|
console.log(`Peso della grafica con swiftshader: ${rows.length} misure; gli arretrati più alti: ${worst}`);
|
|
assert.deepEqual(slow, [], `troppo pesanti con swiftshader (soglie: ${MAX_BACKLOG} ms, ${MIN_FPS} fotogrammi al secondo)`);
|
|
assert.deepEqual(errors, []);
|
|
console.log('Prova del peso: OK');
|
|
} finally {
|
|
await browser?.close();
|
|
server.kill();
|
|
}
|