# 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 | −30–50 % GC-Pressure | | 2 | **Spatial Hash** (Grid 32 px) für `towerFire`-Zielwahl (heute O(N·M) alle Paare) | `towerFire(t,dt)` | −50–80 % 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 | −10–20 % 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 (``-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 | 18–24 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 1–2) | P0 1–7 (Pooling, Spatial Hash, Layer-Cache, dt-Clamp) | ~6 h | | S2 (Tag 3–4) | P1 8–11 (Modularisierung + Konstanten + Store) | ~8 h | | S3 (Tag 5) | P1 12–13 + 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`).