193 lines
7.9 KiB
Markdown
193 lines
7.9 KiB
Markdown
# 02 – Audits im Detail (self / extension / redesign)
|
||
|
||
> Was die drei eingebauten Audits konkret prüfen, welche **Assertions** `tests/audit.mjs` darauf aufsetzt und welche **Exit-Codes** daraus resultieren.
|
||
> API-Basis: [`01_Debug_API.md`](./01_Debug_API.md).
|
||
|
||
---
|
||
|
||
## 1. Übersicht
|
||
|
||
| Audit | API-Funktion | Fokus |
|
||
|---|---|---|
|
||
| **Self** | `runSelfAudit()` | Konnektivität (11 Levels), Attacks, Skills, Platzierung, AutoWave, Endless |
|
||
| **Extension** | `runExtensionAudit()` | Mortar-Splash, Tesla-Chain/Freeze, Reward-Dedup, Blocker-Logik |
|
||
| **Redesign** | `runRedesignAudit()` | Kryo, Sniper (Pierce), Mortar-FixImpact, Quantum-Beam, Nano-Spawner |
|
||
|
||
`audit.mjs` führt alle drei in einer Headless-Chromium-Session aus, sammelt `pageerror`-Events und bewertet die Rückgabe-Werte mit eigenen Assertion-Hilfsfunktionen.
|
||
|
||
---
|
||
|
||
## 2. `runSelfAudit()` – Struktur & Assertions
|
||
|
||
### 2.1 Report-Struktur (Rückgabe der API)
|
||
|
||
```jsonc
|
||
{
|
||
"connectivity": [ { "level": 0, "starts": 2, "exit": true, "reachable": true }, … ], // 11 Levels
|
||
"attacks": [ { "id": "sniper", "projectiles": 3, "holos": 0, "hurt": 2 }, … ],
|
||
"skills": [ { "id": "sniper", "rank": 4 }, … ],
|
||
"placement": { "wallAccepted": true, "pathAccepted": false, "count": 1 },
|
||
"autoWave": { "state": "playing", "active": true },
|
||
"endless": { "state": "endless", "endlessWave": 1 }
|
||
}
|
||
```
|
||
|
||
### 2.2 Assertions in `tests/audit.mjs` (`assertSelfAudit`)
|
||
|
||
| # | Assertion | Erwartung |
|
||
|---|---|---|
|
||
| S-conn | `L{n} has start` | `starts > 0` (pro Level) |
|
||
| S-conn | `L{n} has exit` | `exit === true` (pro Level) |
|
||
| S-conn | `L{n} reachable` | `reachable === true` (pro Level) |
|
||
| S-atk | `tower {id}: projectiles=… holos=… hurt=…` | **info** (Support-/Relay-Türme feuern absichtlich nicht → kein Hard-Fail) |
|
||
| S-sk | `skill {id} rank>=1` | `rank >= 1` (nach 4× `buySkill`) |
|
||
| S-plc | `placement: wallAccepted (tower on free cell)` | `wallAccepted === true` |
|
||
| S-plc | `placement: pathAccepted (tower on path rejected)` | `pathAccepted === false` |
|
||
| S-plc | `placement: tower count>=1` | `count >= 1` |
|
||
| S-aw | `autoWave: state` / `autoWave: active` | **info** (nur State prüfen) |
|
||
| S-ee | `endless: state` | **info** |
|
||
| S-ee | `endless: endlessWave>=0` | `endlessWave >= 0` |
|
||
|
||
> Hinweis: „Attacks“ und „AutoWave“ sind bewusst **Diagnose-/Info-Checks** – die harten Pfade laufen über Konnektivität, Skills und Platzierung.
|
||
|
||
---
|
||
|
||
## 3. `runExtensionAudit()` – Struktur & Assertions
|
||
|
||
### 3.1 Report-Struktur
|
||
|
||
```jsonc
|
||
{
|
||
"mortar": { "projectile": true, "hurt": true },
|
||
"tesla": { "damaged": 3, "frozen": 2 },
|
||
"reward": { "pointsAfterDuplicate": 1, "storedRank": 4, "rankAfterBuy": 4 },
|
||
"blockerAlternate":{ "placed": true, "finite": true, "soldPathRestored": true },
|
||
"blockerClosed": { "placed": true, "destroyed": true, "pathRestored": true }
|
||
}
|
||
```
|
||
|
||
### 3.2 Assertions (`assertExtensionAudit`)
|
||
|
||
| # | Assertion | Erwartung |
|
||
|---|---|---|
|
||
| E-mrt | `mortar: projectile spawned (may resolve instantly)` | **info** |
|
||
| E-mrt | `mortar: both targets hurt (splash)` | `hurt === true` |
|
||
| E-tes | `tesla: damaged enemies count` | `damaged > 1` |
|
||
| E-tes | `tesla: frozen count` | `frozen > 1` |
|
||
| E-rwd | `reward: pointsAfterDuplicate>=1` | `pointsAfterDuplicate >= 1` |
|
||
| E-rwd | `reward: storedRank matches rankAfterBuy` | `storedRank === rankAfterBuy` |
|
||
| E-blt | `blockerAlternate: placed` / `route still finite` / `path restored after sell` | `placed`, `finite`, `soldPathRestored` (falls Kandidat gefunden) |
|
||
| E-blc | `blockerClosed: placed` / `destroyed by enemy` / `path restored` | `placed`, `destroyed`, `pathRestored` (falls Kandidat gefunden) |
|
||
|
||
> Fehlt ein geeigneter Blocker-Kandidat, wird ein **info-Pass** („no candidate found (acceptable)") erzeugt – kein Hard-Fail.
|
||
|
||
---
|
||
|
||
## 4. `runRedesignAudit()` – Struktur & Assertions
|
||
|
||
### 4.1 Report-Struktur
|
||
|
||
```jsonc
|
||
{
|
||
"kryo": { "randomLastTarget": true, "visibleSlow": true },
|
||
"sniper": { "pierced": 3, "noDot": true, "highestAbsoluteHp": true },
|
||
"mortar": { "fixedImpact": true },
|
||
"quantum":{ "lineHits": 3, "offLineUntouched": true, "beam": true },
|
||
"nano": { "spawned": 5, "damaged": true, "cleaned": true }
|
||
}
|
||
```
|
||
|
||
### 4.2 Assertions (`assertRedesignAudit`)
|
||
|
||
| # | Assertion | Erwartung |
|
||
|---|---|---|
|
||
| R-kyo | `kryo: target hit` | `randomLastTarget || visibleSlow` |
|
||
| R-kyo | `kryo: slow applied` | `visibleSlow === true` |
|
||
| R-snp | `sniper: pierced count` | `pierced > 1` |
|
||
| R-snp | `sniper: no DoT (burn/poison)` | `noDot === true` |
|
||
| R-snp | `sniper: highest absolute HP targeting` | `highestAbsoluteHp === true` (falls vorhanden) |
|
||
| R-mrt | `mortar: fixed impact (witness hit, fleeer untouched)` | `fixedImpact === true` |
|
||
| R-qtm | `quantum: line hits` | `lineHits > 1` |
|
||
| R-qtm | `quantum: off-line untouched` | `offLineUntouched === true` |
|
||
| R-qtm | `quantum: beam created` | `beam === true` |
|
||
| R-nno | `nano: spawned count` | `spawned > 1` |
|
||
| R-nno | `nano: damaged target` | `damaged === true` |
|
||
| R-nno | `nano: cleaned up` | `cleaned === true` |
|
||
|
||
---
|
||
|
||
## 5. `audit-result.json` – Schema (authoritative)
|
||
|
||
```jsonc
|
||
{
|
||
"timestamp": "2026-08-28T19:00:00.000Z",
|
||
"file": "…/Aegis-Labyrinth.html",
|
||
"fileHash": "a1b2c3d4e5f60718", // sha256, erste 16 Hex-Zeichen
|
||
"self": { /* Sektion 2.1 */ },
|
||
"extension": { /* Sektion 3.1 */ },
|
||
"redesign": { /* Sektion 4.1 */ },
|
||
"assertions": {
|
||
"self": [ { "name", "actual", "expected", "passed" } ],
|
||
"extension": [ … ],
|
||
"redesign": [ … ]
|
||
},
|
||
"pageErrors": [], // gemeldete JS-Fehler (pageerror-Events)
|
||
"summary": { "pass": 40, "fail": 0, "total": 42 } // total inkl. PageErrors
|
||
}
|
||
```
|
||
|
||
Das Gesamtverdict in `report.mjs` wird direkt aus `summary.fail === 0` **und** `pageErrors.length === 0` abgeleitet.
|
||
|
||
---
|
||
|
||
## 6. Exit-Code & Fail-Verhalten
|
||
|
||
```
|
||
exit 0 → summary.fail === 0 UND pageErrors leer
|
||
exit 1 → ≥ 1 Assertion failed ODER ≥ 1 PageError
|
||
exit 2 → harter Crash (Datei fehlt, Browser-Start, Timeout, …)
|
||
```
|
||
|
||
Zusätzlich werden immer `audit-result.json` **und** `audit-final.png` geschrieben (auch bei Fail), damit die Fehler in `report.html` nachvollziehbar bleiben.
|
||
|
||
---
|
||
|
||
## 7. Fehlerklassen, die die Audits typischerweise fangen
|
||
|
||
| Kategorie | Wo sichtbar | Häufige Ursache |
|
||
|---|---|---|
|
||
| Pfad unvollständig | `self.connectivity[n].reachable === false` | Flow-Field `rebuild()` nach `grid`-Change vergessen |
|
||
| Turm schießt nicht | `self.attacks[id].projectiles === 0` (info) | Cooldown nicht reset / Range zu klein |
|
||
| Skill nicht kaufbar | `self.skills[id].rank < 1` | `buySkill`-Cost > Credits / Skill-ID-Typo |
|
||
| Platzierung falsch | `self.placement.pathAccepted === true` | `placeTower` prüft `grid` nicht |
|
||
| Splash trifft nicht | `extension.mortar.hurt === false` | Splash-Radius / Ziel-Filter |
|
||
| Tesla ohne Effekt | `extension.tesla.frozen < 1` | Freeze-Status nicht gesetzt |
|
||
| Doppelte Rewards | `extension.reward.storedRank !== rankAfterBuy` | `triggerVictory` ohne Idempotenz |
|
||
| Pfad kaputt nach Blocker | `extension.blockerClosed.pathRestored === false` | `sellTower` ruft `Game.flow.rebuild()` nicht auf |
|
||
| Sniper kein Pierce | `redesign.sniper.pierced < 2` | Pierce-Logik fehlt |
|
||
| Quantum trifft Off-Line | `redesign.quantum.offLineUntouched === false` | Winkel-Test zu locker |
|
||
| Naniten lecken | `redesign.nano.cleaned === false` | Cleanup-Flag / `NANITE_LIMIT` |
|
||
|
||
---
|
||
|
||
## 8. Bug-Fix-Routine (empfohlen)
|
||
|
||
```
|
||
1. Replizieren
|
||
→ npm run audit
|
||
→ audit-result.json öffnen → failing Assertion identifizieren
|
||
→ Optional: In-Game-Debug-Console (Taste ` `) → `audit self` / `state`
|
||
|
||
2. Isolation
|
||
→ Spiel-State minimal halten:
|
||
Game.towers = [onlyTower]
|
||
Game.enemies = [onlyEnemy]
|
||
Game.projectiles = []
|
||
→ Einzelne Funktion aufrufen: towerFire(t, 1/60)
|
||
|
||
3. Fixen
|
||
→ Aegis-Labyrinth.html editieren
|
||
|
||
4. Verifizieren
|
||
→ npm run audit erneut → grün
|
||
→ npm run report → report.html prüfen |