#!/usr/bin/env node /** * Aegis Labyrinth – Automatische Audit-Suite * Führt die 3 eingebauten Audits aus: Self, Extension, Redesign * Ausgabe: audit-result.json + Konsolentabelle * * 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 . */ import { chromium } from 'playwright'; import path from 'node:path'; import fs from 'node:fs'; import crypto from 'node:crypto'; import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '..'); const HTML_PATH = path.resolve(ROOT, 'Aegis-Labyrinth.html'); // ─── Helpers ─────────────────────────────────────────────────────── function timestamp() { return new Date().toISOString(); } function sha256(buf) { return crypto.createHash('sha256').update(buf).digest('hex').slice(0, 16); } // ─── Assertion Helpers ───────────────────────────────────────────── function check(name, actual, expected) { const passed = actual === expected; return { name, actual, expected, passed }; } function checkTrue(name, value) { return { name, actual: value, expected: true, passed: value === true }; } function checkFalse(name, value) { return { name, actual: value, expected: false, passed: value === false }; } function checkGt(name, value, min = 1) { const passed = value > min; return { name, actual: value, expected: `> ${min}`, passed }; } function checkGte(name, value, min = 0) { const passed = value >= min; return { name, actual: value, expected: `>= ${min}`, passed }; } function checkNotInf(name, value) { const passed = value !== Infinity && value !== undefined && value !== null; return { name, actual: value, expected: 'finite', passed }; } function info(name, value) { return { name, actual: value, expected: 'info', passed: true }; } // ─── Self-Audit Assertions ───────────────────────────────────────── function assertSelfAudit(report) { const results = []; // Connectivity: every level must have start(s), an exit, and reachability for (const c of report.connectivity) { results.push(checkTrue(`L${c.level} has start`, c.starts > 0)); results.push(checkTrue(`L${c.level} has exit`, c.exit)); results.push(checkTrue(`L${c.level} reachable`, c.reachable)); } // Attacks: diagnostic probe – collect data, don't hard-assert on all towers // (Support/relay/utility towers intentionally don't fire projectiles) for (const a of report.attacks) { results.push(info(`tower ${a.id}: projectiles=${a.projectiles} holos=${a.holos} hurt=${a.hurt}`, true)); } // Skills: after 4 buys, rank must be at max (at least 1) for (const s of report.skills) { results.push(checkGte(`skill ${s.id} rank>=1`, s.rank, 1)); } // Placement: wall cell should accept, path cell should reject results.push(checkTrue('placement: wallAccepted (tower on free cell)', report.placement.wallAccepted)); results.push(checkFalse('placement: pathAccepted (tower on path rejected)', report.placement.pathAccepted)); results.push(checkGte('placement: tower count>=1', report.placement.count, 1)); // AutoWave: informational – just verify it's a valid state results.push(info('autoWave: state', report.autoWave.state)); results.push(info('autoWave: active', report.autoWave.active)); // Endless: verify state is valid, endlessWave is a number results.push(info('endless: state', report.endless.state)); results.push(checkGte('endless: endlessWave>=0', report.endless.endlessWave, 0)); return results; } // ─── Extension-Audit Assertions ──────────────────────────────────── function assertExtensionAudit(report) { const results = []; // Mortar: diagnostic – projectile may be resolved immediately by updateProjectiles results.push(info('mortar: projectile spawned (may resolve instantly)', report.mortar.projectile)); results.push(checkTrue('mortar: both targets hurt (splash)', report.mortar.hurt)); // Tesla: must damage at least one enemy and apply freeze results.push(checkGt('tesla: damaged enemies count', report.tesla.damaged)); results.push(checkGt('tesla: frozen count', report.tesla.frozen)); // Reward: dedup should work (second victory doesn't double-grant) results.push(checkTrue('reward: pointsAfterDuplicate>=1', report.reward.pointsAfterDuplicate >= 1)); results.push(checkTrue('reward: storedRank matches rankAfterBuy', report.reward.storedRank === report.reward.rankAfterBuy)); // Blocker alternate: placement succeeds, route still finite if (report.blockerAlternate.placed) { results.push(checkTrue('blockerAlternate: placed', report.blockerAlternate.placed)); results.push(checkTrue('blockerAlternate: route still finite', report.blockerAlternate.finite)); results.push(checkTrue('blockerAlternate: path restored after sell', report.blockerAlternate.soldPathRestored)); } else { results.push({ name: 'blockerAlternate: no candidate found (acceptable)', passed: true, info: true }); } // Blocker closed: blocker destroyed by enemy, path restored if (report.blockerClosed.placed) { results.push(checkTrue('blockerClosed: placed', report.blockerClosed.placed)); results.push(checkTrue('blockerClosed: destroyed by enemy', report.blockerClosed.destroyed)); results.push(checkTrue('blockerClosed: path restored', report.blockerClosed.pathRestored)); } else { results.push({ name: 'blockerClosed: no candidate found (acceptable)', passed: true, info: true }); } return results; } // ─── Redesign-Audit Assertions ───────────────────────────────────── function assertRedesignAudit(report) { const results = []; // Kryo: hits at least one target, applies slow results.push(checkTrue('kryo: target hit', report.kryo.randomLastTarget || report.kryo.visibleSlow)); results.push(checkTrue('kryo: slow applied', report.kryo.visibleSlow)); // Sniper: pierces multiple enemies, no DoT results.push(checkGt('sniper: pierced count', report.sniper.pierced)); results.push(checkTrue('sniper: no DoT (burn/poison)', report.sniper.noDot)); if (report.sniper.highestAbsoluteHp !== undefined) { results.push(checkTrue('sniper: highest absolute HP targeting', report.sniper.highestAbsoluteHp)); } // Mortar (redesign): fixed impact point results.push(checkTrue('mortar: fixed impact (witness hit, fleeer untouched)', report.mortar.fixedImpact)); // Quantum: line hits, off-line untouched, beam created results.push(checkGt('quantum: line hits', report.quantum.lineHits)); results.push(checkTrue('quantum: off-line untouched', report.quantum.offLineUntouched)); results.push(checkTrue('quantum: beam created', report.quantum.beam)); // Nano: spawned, damaged target, cleaned up results.push(checkGt('nano: spawned count', report.nano.spawned)); results.push(checkTrue('nano: damaged target', report.nano.damaged)); results.push(checkTrue('nano: cleaned up', report.nano.cleaned)); return results; } // ─── Result Printer ──────────────────────────────────────────────── function printResults(name, results) { const pass = results.filter(r => r.passed).length; const fail = results.length - pass; const status = fail === 0 ? '✅' : '❌'; console.log(` ${status} ${name}: ${pass}/${results.length} passed${fail > 0 ? ` (${fail} FAILED)` : ''}`); if (fail > 0) { results.filter(r => !r.passed).forEach(r => { console.log(` ❌ ${r.name}: actual=${JSON.stringify(r.actual)} expected=${r.expected}`); }); } } // ─── Main ────────────────────────────────────────────────────────── async function main() { console.log('═══════════════════════════════════════════════════'); console.log(' AEGIS LABYRINTH – AUTOMATED AUDIT SUITE'); console.log('═══════════════════════════════════════════════════'); console.log(` Datei: ${HTML_PATH}`); console.log(` Zeit: ${timestamp()}\n`); if (!fs.existsSync(HTML_PATH)) { console.error('❌ Aegis-Labyrinth.html nicht gefunden!'); process.exit(1); } const result = { timestamp: timestamp(), file: HTML_PATH, fileHash: sha256(fs.readFileSync(HTML_PATH)), self: null, extension: null, redesign: null, assertions: { self: [], extension: [], redesign: [] }, pageErrors: [], summary: { pass: 0, fail: 0, total: 0 } }; let browser; try { browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const page = await context.newPage(); const pageErrors = []; page.on('pageerror', (err) => pageErrors.push(err.message)); const url = 'file:///' + HTML_PATH.replace(/\\/g, '/'); await page.goto(url, { waitUntil: 'load', timeout: 30000 }); await page.waitForFunction(() => typeof window.__AEGIS_DEBUG__ !== 'undefined', { timeout: 10000 }); console.log(' ✅ Seite geladen, __AEGIS_DEBUG__ verfügbar\n'); // ─── 1. Self-Audit ──────────────────────────────────────────────────────────── console.log('📡 SELF-AUDIT (runSelfAudit)'); result.self = await page.evaluate(() => { try { return window.__AEGIS_DEBUG__.runSelfAudit(); } catch (e) { return { error: e.message }; } }); if (result.self.error) { console.log(` ❌ Error: ${result.self.error}`); } else { result.assertions.self = assertSelfAudit(result.self); printResults('self', result.assertions.self); } console.log(''); // ─── 2. Extension-Audit ────────────────────────────────────────────────────── console.log('🧪 EXTENSION-AUDIT (runExtensionAudit)'); result.extension = await page.evaluate(() => { try { return window.__AEGIS_DEBUG__.runExtensionAudit(); } catch (e) { return { error: e.message }; } }); if (result.extension.error) { console.log(` ❌ Error: ${result.extension.error}`); } else { result.assertions.extension = assertExtensionAudit(result.extension); printResults('extension', result.assertions.extension); } console.log(''); // ─── 3. Redesign-Audit ─────────────────────────────────────────────────────── console.log('🔬 REDESIGN-AUDIT (runRedesignAudit)'); result.redesign = await page.evaluate(() => { try { return window.__AEGIS_DEBUG__.runRedesignAudit(); } catch (e) { return { error: e.message }; } }); if (result.redesign.error) { console.log(` ❌ Error: ${result.redesign.error}`); } else { result.assertions.redesign = assertRedesignAudit(result.redesign); printResults('redesign', result.assertions.redesign); } console.log(''); // ─── 4. Page Errors ────────────────────────────────────────────────────────── console.log('🐛 PAGE ERROR CHECK'); result.pageErrors = pageErrors; if (pageErrors.length === 0) { console.log(' ✅ Keine JS-Fehler\n'); } else { console.log(` ❌ ${pageErrors.length} JS-Fehler:`); pageErrors.slice(0, 5).forEach(e => console.log(` - ${e}`)); console.log(''); } // ─── Screenshot ────────────────────────────────────────────────────────────── const shotPath = path.join(ROOT, 'audit-final.png'); await page.screenshot({ path: shotPath, fullPage: false }).catch(() => {}); if (fs.existsSync(shotPath)) { console.log(` 📸 Screenshot: ${shotPath}`); } // ─── Summary ───────────────────────────────────────────────────────────────── const allAssertions = [ ...result.assertions.self, ...result.assertions.extension, ...result.assertions.redesign ]; const pass = allAssertions.filter(a => a.passed).length; const fail = allAssertions.length - pass; const total = allAssertions.length + pageErrors.length; result.summary = { pass, fail, total }; console.log('═══════════════════════════════════════════════════'); console.log(` ERGEBNIS: ✅ ${pass} PASS ❌ ${fail} FAIL (${allAssertions.length} Checks, ${pageErrors.length} PageErrors)`); console.log('═══════════════════════════════════════════════════'); const outPath = path.join(ROOT, 'audit-result.json'); fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf-8'); console.log(`\n 📄 Ergebnis gespeichert: ${outPath}`); await browser.close(); process.exit(fail > 0 || pageErrors.length > 0 ? 1 : 0); } catch (err) { console.error('\n❌ Audit fehlgeschlagen:', err.message); if (browser) await browser.close().catch(() => {}); const outPath = path.join(ROOT, 'audit-result.json'); fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf-8'); process.exit(2); } } main();