120 lines
4.7 KiB
JavaScript
120 lines
4.7 KiB
JavaScript
// perf-test.mjs – misst FPS (und optional Heap-Δ) unter Last: schweres Level,
|
||
// viele Türme, aktive Welle. Nutzt die öffentliche window.__AEGIS_DEBUG__-API.
|
||
//
|
||
// Copyright (C) 2026 Lila-Kuh
|
||
//
|
||
// This program is free software: you can redistribute it and/or modify
|
||
// it under the terms of the GNU General Public License as published by
|
||
// the Free Software Foundation, either version 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// This program is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU General Public License
|
||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
//
|
||
// WICHTIG: Global-scope Identifier (TOWER_TYPES, Game, placeTower) müssen in
|
||
// page.evaluate() als BARE IDENTIFIER referenziert werden (nicht window.X).
|
||
//
|
||
// Usage: node perf-test.mjs [durationMs=15000] [towers=50]
|
||
import { chromium } from 'playwright';
|
||
import path from 'path';
|
||
import fs from 'fs';
|
||
import url from 'url';
|
||
|
||
const HERE = path.dirname(url.fileURLToPath(import.meta.url));
|
||
const ROOT = path.resolve(HERE, '..');
|
||
const HTML = path.resolve(ROOT, 'Aegis-Labyrinth.html').replace(/\\/g, '/');
|
||
const DURATION_MS = Math.max(1000, parseInt(process.argv[2] || '15000', 10));
|
||
const TOWERS = Math.max(1, parseInt(process.argv[3] || '50', 10));
|
||
|
||
const browser = await chromium.launch({ headless: true });
|
||
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
||
page.on('pageerror', (e) => console.error('[PAGE ERROR]', e.message));
|
||
|
||
await page.goto('file:///' + HTML, { waitUntil: 'load' });
|
||
await page.waitForFunction(() => window.__AEGIS_DEBUG__ !== undefined, { timeout: 15000 });
|
||
|
||
// Setup: schweres Level (letztes), Credits, Türme, Welle.
|
||
const setup = await page.evaluate(({ towers }) => {
|
||
const d = window.__AEGIS_DEBUG__;
|
||
const levelIdx = Math.min(LEVELS.length - 1, 10); // Level 11 (0-basiert Index 10)
|
||
d.selectLevel(levelIdx);
|
||
if (Game.state !== 'playing') Game.state = 'playing';
|
||
d.setCredits(9999999);
|
||
|
||
// Pfad & buildbare Zellen ermitteln.
|
||
const path = [];
|
||
for (let y = 0; y < MAP_H; y++)
|
||
for (let x = 0; x < MAP_W; x++) if (Game.grid[y][x] === 1) path.push([x, y]);
|
||
|
||
const towerTypes = TOWER_TYPES.filter(isTowerAvailable).filter((t) => isTowerUnlocked(t));
|
||
const candidates = [];
|
||
for (let y = 0; y < MAP_H; y++)
|
||
for (let x = 0; x < MAP_W; x++) {
|
||
if (Game.grid[y][x] !== 0) continue;
|
||
let best = Infinity;
|
||
for (const [px, py] of path) best = Math.min(best, Math.hypot(px - x, py - y));
|
||
if (best < CELL * 3.01) candidates.push({ x, y, best });
|
||
}
|
||
candidates.sort((a, b) => a.best - b.best);
|
||
|
||
let placed = 0;
|
||
for (let i = 0; i < candidates.length && placed < towers; i++) {
|
||
const def = towerTypes[i % Math.max(1, towerTypes.length)];
|
||
if (!def) break;
|
||
if (placeTower(def.id, candidates[i].x, candidates[i].y) === true) placed++;
|
||
}
|
||
d.startWave();
|
||
const s = d.getState();
|
||
return { levelIdx, levelName: s.levelName, placed, enemies: s.enemies, state: s.state };
|
||
}, { towers: TOWERS });
|
||
console.log(`Setup: Level=${setup.levelName} (idx ${setup.levelIdx}) · placed=${setup.placed} · enemies=${setup.enemies} · state=${setup.state}`);
|
||
|
||
// FPS messen: zähle requestAnimationFrame-Callbacks über DURATION_MS.
|
||
const fps = await page.evaluate((durationMs) => {
|
||
return new Promise((resolve) => {
|
||
let frames = 0;
|
||
const start = performance.now();
|
||
function tick() {
|
||
frames++;
|
||
if (performance.now() - start < durationMs) {
|
||
requestAnimationFrame(tick);
|
||
} else {
|
||
const elapsed = (performance.now() - start) / 1000;
|
||
resolve(Math.round(frames / elapsed));
|
||
}
|
||
}
|
||
requestAnimationFrame(tick);
|
||
});
|
||
}, DURATION_MS);
|
||
|
||
// Heap-Δ (falls available – Chromium performance.memory).
|
||
const heap = await page.evaluate(() => {
|
||
const m = (typeof performance !== 'undefined' && performance.memory) ? performance.memory : null;
|
||
return m
|
||
? { usedMB: +(m.usedJSHeapSize / 1048576).toFixed(1), totalMB: +(m.totalJSHeapSize / 1048576).toFixed(1) }
|
||
: null;
|
||
});
|
||
|
||
const result = {
|
||
timestamp: new Date().toISOString(),
|
||
durationMs: DURATION_MS,
|
||
fps,
|
||
targetFps: 55,
|
||
pass: fps >= 55,
|
||
heap,
|
||
setup,
|
||
};
|
||
const outFile = path.join(ROOT, 'perf-result.json');
|
||
fs.writeFileSync(outFile, JSON.stringify(result, null, 2));
|
||
const icon = result.pass ? '✅' : '❌';
|
||
console.log(`${icon} Average FPS: ${result.fps} (target ≥ ${result.targetFps})`);
|
||
if (heap) console.log(` Heap: used=${heap.usedMB} MB`);
|
||
console.log(`✅ Perf done → ${outFile}`);
|
||
|
||
await browser.close();
|
||
process.exitCode = result.pass ? 0 : 1; |