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
+337
View File
@@ -0,0 +1,337 @@
#!/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 <https://www.gnu.org/licenses/>.
*/
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();