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
+123
View File
@@ -0,0 +1,123 @@
# 06 Refactoring & Performance (Advisory für `Aegis-Labyrinth.html`)
> **Advisory-Dokumentation** Empfehlungen, `Aegis-Labyrinth.html` (~707 KB, Single-File-Game)
> sauberer, schneller und wartbarer zu machen, **ohne** das sichtbare Spielverhalten zu ändern.
>
> ⚠️ Diese Datei gehört **nicht** zur Test-Suite selbst sie ist ein **Beratungs-Backlog** für die
> extern eingelegte Game-Datei. Die Suite (`tests/*`) prüft nur, dass Änderungen daran die 3 Audits
> nicht brechen (→ `02_Audits.md`) und die Performance nicht einbricht (→ `04_Resultate_Report.md`).
---
## 1. Ist-Zustand (kurz)
| Aspekt | Status |
|---|---|
| Struktur | Eine HTML-Datei: CSS + ~4.500 Zeilen JS + UI |
| Rendering | Canvas 2D, `requestAnimationFrame`-Loop, Partikel-/Projectile-Arrays |
| State | Globale `Game`-Objekte + freie Funktionen im Global Scope |
| Persistenz | `localStorage` (Skilltree, Audio, Unlocks, Custom Levels) |
| Debug | `window.__AEGIS_DEBUG__` (Audits, State, Audio, Levels) → `01_Debug_API.md` |
| Tests | 3 eingebauten Audits + externe Suite (`tests/`) |
| Bundle | Base64-PNG-Logo (ca. 350 KB) inline bereits optimiert |
---
## 2. Priorisierte Maßnahmen (P0 = mach heute, P2 = Backlog)
### P0 Hohe Wirkung, geringes Risiko
| # | Maßnahme | Wo | Erwarteter Effekt |
|---|---|---|---|
| 1 | **Partikel-/Projectile-Pooling** statt `push`/`filter` pro Frame | `updateProjectiles`, `updateEnemies`, Partikel-Arrays | 3050 % GC-Pressure |
| 2 | **Spatial Hash** (Grid 32 px) für `towerFire`-Zielwahl (heute O(N·M) alle Paare) | `towerFire(t,dt)` | 5080 % CPU bei 50+ Türmen |
| 3 | **Canvas-Layer-Trennung**: `bgCanvas`, `midCanvas`, `fxCanvas` + `ctx.imageSmoothingEnabled=false` | `render()` | Weniger Re-Draws, stabilerer 60 fps |
| 4 | **Offscreen-Canvas für statische Route** (1× pro Level zeichnen, dann `drawImage`) | Route-Drawing | 1020 % Framezeit |
| 5 | **Delta-Clamp**: `dt = Math.min(dt, 1/30)` am Loop-Start | Game-Loop | Keine Explosionen nach Tab-Wechsel |
| 6 | **`willReadFrequently:true`** nur bei `getImageData`-Puffern, `desynchronized:true` bei FX-Canvas | Canvas-Kontexte | Geringere GPU-Roundtrips |
| 7 | **Event-Debounce** auf `mousemove` (Tooltip) & `resize` | HUD/Tooltip | Weniger Layout-Thrashing |
### P1 Mittlere Wirkung
| # | Maßnahme |
|---|---|
| 8 | **Modularisierung** in IIFE/Module: `core/loop`, `core/state`, `units/tower`, `units/enemy`, `systems/pathflow`, `systems/aura`, `systems/skills`, `ui/*`, `audio`, `persistence`, `debug` |
| 9 | **State-Reduktion**: `Game`-Objekt durch `store`-Muster mit `subscribe/get` ersetzen (~60 Zeilen) |
| 10 | **Pure-Function-Tests für Pfadlogik**: `flowField`, `diagnoseRouteState`, `computePath` extrahieren → unit-testbar |
| 11 | **Konstanten-Block** (`CELL`, `MAP_W`, `NANITE_LIMIT`, `PARTICLE_MAX`, …) in `constants` |
| 12 | **Magic-Number-Audit**: `1/60`, `0.05`, `180` etc. in Konstanten umbenennen |
| 13 | **`Symbol()`-Keys** für interne `Game.towers[i]._internal*` statt String-Props |
### P2 Backlog
| # | Maßnahme |
|---|---|
| 14 | Web Worker für Flow-Field-Berechnung bei großen Maps |
| 15 | WebGL2-Option (`<canvas>`-Fallback bleibt) für Partikel > 5.000 |
| 16 | `requestIdleCallback` für UI-Rebuilds (Tower-Panel, Skilltree) |
| 17 | Code-Splitting via `import()` hinter „Menu" |
| 18 | `Intl.NumberFormat` für Zahlen im HUD |
| 19 | i18n-Datei (DE/EN) statt inline-Strings |
| 20 | PWA-Shell (`manifest.json`, `sw.js`) für Offline |
---
## 3. Rendering-Optimierung (Details)
### 3.1 Frame-Pipeline (empfohlen)
```
[1] input → pointer events, key state
[2] update(dt) → enemies → towers → projectiles → nanites → particles
[3] dirty check → bgDirty / midDirty / fxDirty (Boolean pro Layer)
[4] render() → if bgDirty: drawImage(bgCanvas);
if midDirty: draw towers+enemies;
fxCanvas always (additive)
```
### 3.2 Konkrete Canvas-Techniken
```js
const fx = document.createElement('canvas');
const fctx = fx.getContext('2d', { alpha: true, desynchronized: true });
fctx.globalCompositeOperation = 'lighter'; // Partikel additiv
// pro Frame:
fctx.clearRect(0, 0, fx.width, fx.height); // oder 'destination-out' Fade für Trails
```
- **Batching**: Alle Partikel gleicher Farbe in einen `beginPath()` → eine `fill()`.
- **`roundRect`** nativ (Chrome 99+) statt Hand-Rotation.
- **`createPattern`** für Tile-Boden (1× erzeugen).
- **Sprite-Atlas** für die 9 Türme: 1 Atlas 512×512, `drawImage(atlas, sx,sy,sw,sh, x,y,w,h)`.
### 3.3 Zielwerte
| Metrik | Vorher (Messung in DevTools) | Nachher (Ziel) |
|---|---|---|
| Framezeit @ 4K, 80 Türme, 150 Feinde | 1824 ms | ≤ 12 ms |
| GC-Pausen/Minute | ~12 | ≤ 4 |
| Heap-Wachstum (10 Min) | ~40 MB | ≤ 10 MB |
---
## 4. Refactoring-Checkliste (Definition of Done)
- [ ] Alle drei Audits (`self`, `extension`, `redesign`) vor & nach Refactoring grün
- [ ] `window.__AEGIS_DEBUG__.getState()` liefert identisches Schema (nur interne Keys dürfen sich ändern)
- [ ] Kein sichtbarer Frame-Drop bei 60 fps Test-Level
- [ ] `localStorage`-Schema unverändert (oder Migration in `persistence.js`)
- [ ] Editor-Map & Custom-Level import/export byte-identisch
- [ ] Lighthouse-Performance ≥ 95 (mobile emulation)
---
## 5. Vorschlag: Refactoring in 3 Sprints
| Sprint | Inhalt | Aufwand |
|---|---|---|
| S1 (Tag 12) | P0 17 (Pooling, Spatial Hash, Layer-Cache, dt-Clamp) | ~6 h |
| S2 (Tag 34) | P1 811 (Modularisierung + Konstanten + Store) | ~8 h |
| S3 (Tag 5) | P1 1213 + P2 14 (Worker) + Lighthouse-Polish | ~5 h |
**Regressionsschutz:** Vor jedem Sprint einen „Golden Frame" (Screenshot + `getState()`-Dump) mit der
Test-Suite erzeugen (`tests/audit.mjs` + `tests/perf-test.mjs`, siehe `03_Automatisierung_CI.md`).