Files
Laby/tests/perf-test.mjs
T
Lila-Kuh ea2a2f2cbf
Aegis CI / audit (push) Failing after 1m7s
Aegis CI / selfplay (push) Skipped
Aegis CI / perf (push) Failing after 3s
Aegis CI / report (push) Failing after 2s
Initial project version
2026-08-29 01:45:19 +02:00

120 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;