8.1 KiB
8.1 KiB
03 – Automatisierung, Self-Play, Performance & CI
Wie die Test-Suite aufgerufen wird, was
selfplay.mjsundperf-test.mjskonkret messen und wie die Stufen in eine CI-Pipeline eingebunden werden.
1. Aufruf der Skripte
1.1 npm-Scripts (aus package.json)
| Script | Aufruf | Parameter (optional) |
|---|---|---|
npm run audit |
node tests/audit.mjs |
– |
npm run selfplay |
node tests/selfplay.mjs [ep] |
ep = Episoden (Default 10) |
npm run perf |
node tests/perf-test.mjs [ms] [towers] |
ms = Dauer (Default 15000), towers = Anzahl (Default 50) |
npm run report |
node tests/report.mjs |
– |
npm test |
Audit + Self-Play + Perf | – |
npm run setup |
npx playwright install chromium |
– |
1.2 Direkt-Aufruf (nützlich für CI / Skripte)
node tests/selfplay.mjs 50 # 50 Episoden
node tests/perf-test.mjs 30000 80 # 30 s messen, 80 Türme
node tests/audit.mjs # Standard-Audit
node tests/report.mjs # Report erzeugen
1.3 Exit-Codes (Übersicht)
| Skript | exit 0 | exit ≠ 0 |
|---|---|---|
audit.mjs |
alle Assertions grün | ≥ 1 Fehler → 1, Crash → 2 |
selfplay.mjs |
(liefert immer 0) | – |
perf-test.mjs |
FPS ≥ Zielwert (55) | FPS < Zielwert → 1 |
report.mjs |
(liefert immer 0) | – |
2. Self-Play (tests/selfplay.mjs)
2.1 Zweck
Deterministischer Bot spielt Level 0 (Standard) oder ein beliebiges Level durch und misst:
- Win-Rate über N Episoden
- Durchschnittliche Kills, Leaks, Credits, Waves
- Stabilität (keine JS-Page-Errors, keine Freeze-Detection)
2.2 Strategie des Bots
1. Level laden (selectLevel, Default Level 0)
2. Credits setzen (setCredits → 999999)
3. Türme platzieren:
- Alle Bau-Kacheln (grid==0) identifizieren
- In Priorität: Sniper, Tesla, Quantum, Nano, Barrier, Holo, Grav, Mortar, Sniper
- Je nach Credits: mehrere Runden × 5 Türme
4. Wave starten (startWave)
5. simulate() in 1/60-Schritten bis victory/defeat
6. Episoden-Log: kills, leaks, credits, waves, duration
7. Aggregation: Win-Rate, mean, std, min, max
2.3 Ausgabe (selfplay-results.json)
{
"timestamp": "2026-08-28T19:30:00.000Z",
"episodes": [
{
"placed": 40,
"towerCount": 40,
"maxKills": 120,
"waveReached": 8,
"totalUpgrades": 64,
"finalState": "victory",
"finalKills": 120,
"finalLeaked": 0,
"finalLives": 20
}
],
"summary": {
"total": 10,
"wins": 8,
"losses": 2,
"winRate": 80.0,
"avgKills": 118,
"avgWaves": 7.4
}
}
2.4 Playwright-Fallstricke (Self-Play)
| Problem | Lösung |
|---|---|
window.TOWER_TYPES → undefined |
TOWER_TYPES (bare Identifier) |
window.placeTower → undefined |
placeTower (bare) |
window.buySkill → undefined |
buySkill (bare) |
window.__AEGIS_DEBUG__ → ✅ |
window.__AEGIS_DEBUG__ |
selfplay.mjsnutzt ausschließlichwindow.__AEGIS_DEBUG__für State-Abfragen und bare Identifier für Aktionen.
3. Performance (tests/perf-test.mjs)
3.1 Zweck
Messen der Frames per Second (FPS) und Heap-Größe unter definiertem Lastprofil:
- Level: schweres Level (Default Level 10, 11 Waves)
- Türme: konfigurierbare Anzahl (Default 50, empfohlen 50–80)
- Dauer: konfigurierbare Messzeit (Default 15000 ms)
3.2 Vorgehen
1. Level laden (selectLevel(10))
2. Credits setzen (setCredits → 999999)
3. Türme platzieren (N Türme auf Bau-Kacheln, Priorität: schwerste zuerst)
4. Wave starten (startWave)
5. simulate() in 1/60-Schritten
6. FPS messen:
- requestAnimationFrame in Page (window.__fps_frames++)
- Alle 1000 ms: fps = frames / 1.0
- Frame-Timing: performance.now() Differenz
7. Heap messen:
- page.evaluate(() => performance.memory)
- usedJSHeapSize / totalJSHeapSize / jsHeapSizeLimit
8. Ergebnis: perf-result.json
3.3 FPS-Zielwert & Exit-Code
| FPS | Bewertung | Exit-Code |
|---|---|---|
| ≥ 55 | ✅ Gut | 0 |
| 45–54 | ⚠️ Verändert | 1 |
| < 45 | ❌ Kritisch | 1 |
3.4 Ausgabe (perf-result.json)
{
"timestamp": "2026-08-28T19:45:00.000Z",
"durationMs": 15000,
"fps": 58,
"targetFps": 55,
"pass": true,
"heap": { "usedMB": 48.3, "totalMB": 96.0 },
"setup": {
"levelIdx": 10,
"levelName": "…",
"placed": 50,
"enemies": 40,
"state": "playing"
}
}
3.5 Bekannte Performance-Problemzonen (→ 06_Refactoring_Game.md)
| Problem | Wo sichtbar | Empfehlung |
|---|---|---|
| Particles | Game.particles wächst > 200 |
Object-Pooling + Max-Limit |
| Projectiles | Game.projectiles > 100 |
Spatial-Partitioning |
| Holograms | Game.holograms > 20 |
Batch-Rendering |
| Beam | Game.beams > 10 |
Line-Rendering statt Circle |
| Enemy-Update | O(n²) bei 20+ Feinden | Spatial-Hash |
4. CI-Workflows (empfohlen)
4.1 Stufen
| Stufe | Skripte | Wann | Dauer (ca.) |
|---|---|---|---|
| Pre-Commit | npm run audit |
bei jedem Commit | 30–60 s |
| Nightly | Audit + selfplay.mjs 50 + perf-test.mjs |
zeitplangetrieben | 5–10 min |
| Release | Audit + selfplay.mjs 500 + perf-test.mjs 30000 80 + Report |
Tagging | 15–30 min |
4.2 GitHub-Actions-Beispiel (.github/workflows/ci.yml)
name: Test-Suite
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 2 * * *' # Nightly 2:00 UTC
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm install
- name: Install Playwright Chromium
run: npx playwright install chromium
# ⚠️ Aegis-Labyrinth.html muss als Secret/Artifact bereitgestellt werden
- name: Download Game-Datei
run: |
echo "${{ secrets.AEGIS_GAME_BASE64 }}" | base64 -d > Aegis-Labyrinth.html
- name: Run Audit
run: npm run audit
continue-on-error: true
- name: Run Self-Play (10 Episoden)
run: node tests/selfplay.mjs 10
- name: Run Performance Test
run: node tests/perf-test.mjs 15000 50
- name: Generate Report
run: node tests/report.mjs
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: test-results
path: |
audit-result.json
selfplay-results.json
perf-result.json
report.html
audit-final.png
# Audit muss grün sein für Main-Push
- name: Check Audit Exit Code
if: github.event_name == 'push' && contains(github.ref, 'refs/heads/main')
run: |
# audit-result.json nutzt "summary": { "pass", "fail", "total" }
if [ ! -f audit-result.json ] || grep -q '"fail": [1-9]' audit-result.json; then
echo "❌ Audit failed"
exit 1
fi
4.3 Game-Datei in CI bereitzustellen
Da Aegis-Labyrinth.html nicht im Repo liegt, muss sie in der CI als Secret oder Artifact bereitgestellt werden:
| Methode | Vorteil | Nachteil |
|---|---|---|
| GitHub Secret (Base64) | Einfach, sicher | Base64-Konvertierung nötig |
| Release Artifact | Versioniert | Extra Schritt zum Herunterladen |
| Separates Private Repo | Saubere Trennung | Access-Token nötig |
Empfehlung: GitHub Secret für einfache CI, separates Private Repo für Teams.
5. Troubleshooting
| Symptom | Ursache | Lösung |
|---|---|---|
❌ Aegis-Labyrinth.html nicht gefunden! |
Datei fehlt | Datei in Projekt-Root kopieren |
browserType.launch: Executable doesn't exist |
Playwright nicht installiert | npx playwright install chromium |
selfplay.mjs liefert immer 0 |
Bot verliert immer | Level prüfen / mehr Credits / Türme |
perf-test.mjs FPS < 45 |
Zu viele Entities | Level/Türme reduzieren / Refactoring |
report.html zeigt "no data" |
*-result.json fehlt |
Audit/Self-Play/Perf erst ausführen |
Playwright evaluate → undefined |
window.X statt X |
Bare Identifier verwenden (siehe 01_Debug_API.md) |