# 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