// 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 . // // 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;