Initial project version
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

This commit is contained in:
2026-08-29 01:45:19 +02:00
commit ea2a2f2cbf
21 changed files with 2830 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
// 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;