Files
Laby/docs/03_Automatisierung_CI.md
Lila-Kuh ea2a2f2cbf
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
Initial project version
2026-08-29 01:45:19 +02:00

276 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 03 Automatisierung, Self-Play, Performance & CI
> Wie die Test-Suite aufgerufen wird, was `selfplay.mjs` und `perf-test.mjs` konkret 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)
```bash
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`)
```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.mjs` nutzt ausschließlich `window.__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 5080)
- **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 |
| 4554 | ⚠️ Verändert | 1 |
| < 45 | ❌ Kritisch | 1 |
### 3.4 Ausgabe (`perf-result.json`)
```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 | 3060 s |
| **Nightly** | Audit + `selfplay.mjs 50` + `perf-test.mjs` | zeitplangetrieben | 510 min |
| **Release** | Audit + `selfplay.mjs 500` + `perf-test.mjs 30000 80` + Report | Tagging | 1530 min |
### 4.2 GitHub-Actions-Beispiel (`.github/workflows/ci.yml`)
```yaml
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`) |