Mindflux: Sicherheit, Datenintegrität und Navigation härten

This commit is contained in:
AliceDarkest
2026-07-12 21:47:37 +02:00
parent db6b5bbb1a
commit 005d2d7d8c
73 changed files with 2495 additions and 650 deletions
+506
View File
@@ -0,0 +1,506 @@
# Mindflux – Detailpläne für die 4 geplanten Spiele + Verbesserungsplan
## Kontext
Das Dashboard von Mindflux (`mindflux2`, Spring Boot 3.4.4 / Java 17 / PostgreSQL + Flyway / Thymeleaf / Vanilla-JS) zeigt 9 Spiele. Vier davon sind nur Platzhalter-Karten, die auf `/comingsoon` (`dummy.html`) zeigen und **keinerlei Mechanik, Controller, Tabellen oder Inhalte** besitzen:
1. **Code Escape Room** – „Entkomme mit IT-Rätseln aus digitalen Räumen – sei schnell und clever."
2. **Service Clash** – „Der chaotische Admin-Alltag als Spiel – meistere Anfragen & halte Systeme am Laufen!"
3. **Bug Hunter** – „Finde Fehler im Code, denke wie ein Debugger und löse knifflige Bugs."
4. **Netzwerker – Das Router-Rätselspiel** – „Verbinde Netzwerke, konfiguriere IPs und löse Routing-Herausforderungen."
Die Infrastruktur wartet bereits auf sie: Die Spiel-Keys `escape-room`, `service-clash`, `bug-hunter`, `netzwerker` stehen schon in der Score-Allowlist (`GameScoreController.KNOWN_GAMES`, Zeile 23–25) und in `ProgressService.GAMES` (Zeile 23–26); die Dashboard-Icons (`lock`, `sword`, `bug`, `network`) existieren in `dashboard.html`. Ziel dieses Plans: detaillierte, umsetzungsreife Designs für alle vier Spiele nach dem etablierten Architektur-Rezept der vorhandenen Spiele (MindMatch, TechTacular, Syntax Saga, Dev Lab) sowie ein priorisierter Verbesserungs-/Optimierungskatalog für den Rest der Plattform.
**Zielgruppe & inhaltlicher Anspruch: die GESAMTE Fachinformatiker-Ausbildung.** Nicht nur AP1 – abgedeckt werden muss die komplette Ausbildung bis einschließlich **AP2** (beide Fachrichtungen: Anwendungsentwicklung/FIAE und Systemintegration/FISI). Konsequenzen für alle Spiele und Inhalte:
- **Schwierigkeitsstufen = Ausbildungsprogression.** Die in allen vier Designs vorhandenen Stufen (Junior/Senior/Principal, leicht/mittel/schwer, Welten 1–3, Schicht 1–3) werden inhaltlich gemappt: untere Stufen ≈ 1. Lehrjahr/AP1-Stoff, obere Stufen ≈ AP2-Niveau. Kein Schema-Umbau nötig – die `difficulty`-Felder existieren in allen Datenmodellen bereits; es ist eine **Content-Kuratierungs-Regel**.
- **AP2-Content pro Spiel (Startrichtung):** CodeEscape → Szenarien zu Verschlüsselung/Zertifikaten, Virtualisierung/Container, Backup-Strategien; BugHunter → Principal-Fälle auf FIAE-AP2-Niveau (Nebenläufigkeit/Threads, Transaktions-/SQL-Fehler, Design-Pattern-Missbrauch, OWASP-Lücken); ServiceClash → Schichten mit Change-Management, Monitoring/Eskalationsketten, Security-Incidents (FISI-AP2); NetWorker → Welten zu VLAN, statischem Routing über mehrere Hops, Firewall-Regeln und (Ausbau) IPv6/Subnetting-Transfer.
- **Content-Tagging:** Beim Kuratieren jedes Inhalts (Seeder-JSONs, Admin-Eingaben) wird vermerkt, ob er AP1-, AP2/FIAE- oder AP2/FISI-relevant ist – zunächst als Konvention im Titel/`description` (z. B. Präfix „[AP2]"), später optional als eigenes Feld + Filter in den Lobbys („Zeige nur AP2-Stoff").
- **FinalFlux erweitern:** Das bestehende Lernmodul ist rein AP1-fokussiert (28 Seiten + FluxChecks). Die Struktur (statische Themenseiten + Frage-JSONs) trägt problemlos einen **AP2-Ast** – als eigener Sidebar-Block „AP2 Anwendungsentwicklung" / „AP2 Systemintegration" (in Teil 2 als Punkt S10 aufgenommen).
- Die vorhandenen FinalFlux-Inhalte (IP-Berechnung, VLAN, OSI, Wirtschaft) bleiben die AP1-Basis, auf der die Spiele aufsetzen.
**Deliverable:** Dieser Plan liegt als `mindflux2/mindfluxfuture.md` im Repo (neben `STYLEGUIDE.md`/`CHANGELOG.md`).
---
## Namensschema: einheitlich, englisch, zusammengeschrieben
> ✅ **Umgesetzt am 2026-07-12**: Anzeigenamen und Spiel-Keys sind auf das Schema umgestellt (Dashboard-`GAMES`-Array, `KNOWN_GAMES`, `ProgressService`).
Vorgabe: Alle Spielnamen folgen dem MindMatch-Muster – **ein Wort, englisch, CamelCase**. „Netzwerker – Das Router-Rätselspiel" passt nicht ins Schema und wird umbenannt. Neue Namenstabelle (Beschreibungen bleiben deutsch):
| Bisher | Neu (Anzeigename) | Route | Spiel-Key (DB/API) |
|--------|-------------------|-------|--------------------|
| MindMatch | **MindMatch** ✓ | `/mindmatch` | `mindmatch` |
| TechTacular | **TechTacular** ✓ | `/techtacular` | `techtacular` |
| Dev Lab | **DevLab** | `/devlab` | `devlab` |
| Syntax Saga | **SyntaxSaga** | `/syntaxsaga` | `syntax-saga` (Key behalten – es existieren bereits Scores) |
| FinalFlux | **FinalFlux** ✓ | `/finalflux` | – |
| Code Escape Room | **CodeEscape** | `/codeescape` | `codeescape` |
| Service Clash | **ServiceClash** | `/serviceclash` | `serviceclash` |
| Bug Hunter | **BugHunter** | `/bughunter` | `bughunter` |
| Netzwerker – Das Router-Rätselspiel | **NetWorker** | `/networker` | `networker` |
Begründung Keys: Die vorhandenen Routen sind alle klein und zusammengeschrieben (`/mindmatch`, `/syntaxsaga` …) – die neuen Routen folgen dem. Die vier reservierten Keys (`escape-room`, `service-clash`, `bug-hunter`, `netzwerker`) haben **noch keine Scores in der DB** und werden daher jetzt kostenlos auf das einheitliche Schema umbenannt (`codeescape`, `serviceclash`, `bughunter`, `networker`) – zu ändern in `GameScoreController.KNOWN_GAMES`, `ProgressService.GAMES` und den jeweiligen Service-`GAME`-Konstanten. Nur `syntax-saga` behält seinen Key (Bestandsdaten), Anzeigename wird trotzdem `SyntaxSaga`. Anzeigenamen anpassen in: `dashboard.html`-`GAMES`-Array, `ProgressService.GAMES`, Seitentiteln/Templates der neuen Spiele. _Alle nachfolgenden Abschnitte verwenden bereits die neuen Namen; wo im Text noch V9–V12-Design-Details Alt-Keys nennen, gilt die Tabelle hier._
---
## Visuelle Identität: vier Spiele, vier Welten
> ☐ **Noch offen** – wird mit dem Bau der Spiele umgesetzt; auch die Spiel-Akzent-Tokens in `theme.css` sind noch nicht angelegt.
Vorgabe: Die Spiele sollen **grafisch stark ausgeprägt** sein und sich **sehr deutlich** voneinander unterscheiden – nicht nur mechanisch, sondern auf den ersten Blick. Dazu zwei Maßnahmen:
**1. Kontrollierte Styleguide-Erweiterung – Spiel-Akzente:** `theme.css` bekommt pro Spiel ein Akzent-Token-Paar (z. B. `--esc-accent`, `--bh-accent`, `--sc-accent`, `--nw-accent`, je mit Dark-/Light-Wert). Jedes Spiel-CSS überschreibt im Seiten-Root lokal `--accent`/`--glow-*` mit seinem Spiel-Akzent. Die Grundregeln bleiben: Erfolg = blaues ✓, Fehler = rotes ✗, nie grün als Erfolgsfarbe; beide Themes müssen funktionieren.
**2. Eigenes visuelles Vokabular pro Spiel** (Layout, Illustration, Animation, Typografie-Akzente):
| Spiel | Welt-Metapher | Farbstimmung | Prägende visuelle Elemente |
|-------|---------------|--------------|---------------------------|
| **CodeEscape** | Dunkle Facility bei Nacht | Bernstein/Orange auf Tiefschwarz | CRT-Terminals mit Scanline-Overlay, physisches Keypad mit Tasten-Feedback, Raum-„Bühnenbild" als SVG-Illustration (Racks, Türen, flackernde Neonröhre), Fragment-Chips wie Hardware-Token, Alarm-Puls bei Zeitablauf, Tür-Öffnungs-Animation zwischen Räumen |
| **BugHunter** | Noir-Detektivbüro | Entsättigt + Crimson-Akzente | Fallakten-Optik (Aktendeckel, Büroklammer, „VERTRAULICH"-Stempel), IDE-Look mit Zeilennummern-Gutter und Tab-Reitern, roter Ermittlungsfaden zum markierten Bug, „GELÖST"-Stempel-Animation im Debriefing, Schreibmaschinen-Typo für Briefings |
| **ServiceClash** | Hektischer Leitstand/Cockpit | Kühles Cyan + Warnrot | Kanban-artige Ticket-Queue mit einfliegenden Karten, große SLA-Ring-Gauges und Gesundheits-Meter, laufender Event-Ticker unten, Wellen-Countdown-Banner, komisch überzeichnete Ticket-Icons (brennender Drucker 🔥), Screen-Shake bei Major Incidents |
| **NetWorker** | Technische Blaupause | Blueprint-Blau auf Millimeterpapier | Raster-Hintergrund wie Konstruktionszeichnung, Geräte als Schematic-Glyphen, Gummiband-Kabelzug, leuchtende Paket-Punkte, die den echten Trace entlangfliegen und am Fehler-Hop rot zerplatzen, Diagnose-Callouts wie Plan-Anmerkungen |
Dazu je Spiel ein eigenes Karten-Design auf dem Dashboard (Hover-Animation im jeweiligen Spiel-Akzent), damit die Unterscheidbarkeit schon in der Übersicht beginnt. Die Interaktionsformen sind ohnehin grundverschieden: Modal-Rätsel + Keypad (CodeEscape), klickbarer Code-Viewer (BugHunter), Echtzeit-Queue (ServiceClash), frei bespielbare SVG-Canvas (NetWorker).
---
## Das gemeinsame Architektur-Rezept (gilt für alle 4 Spiele)
Jedes neue Spiel folgt exakt dem Muster der vorhandenen Spiele (bester Bauplan: **Dev Lab**, `DevLabService.java` + `devlabgame.js`):
1. **Flyway-Migration** `db/migration/V9__…` ff. (V8 ist die letzte; `ddl-auto=validate` ⇒ Migration ist Pflicht). SQL-Stil: `BIGINT GENERATED BY DEFAULT AS IDENTITY`, `slug VARCHAR UNIQUE`, `sort_order INT DEFAULT 0`, `active BOOLEAN DEFAULT TRUE`, `FK … ON DELETE CASCADE`, Indizes.
2. **Entities** in `model/` (Parent mit `@OneToMany(cascade=ALL, orphanRemoval=true)`, Lösungs-Logik nur serverseitig auf der Entity).
3. **Repository** in `repository/` (`findAllByActiveTrueOrderBySortOrderAscIdAsc()`, `findBySlug`, `countBy…Id`).
4. **Service** in `service/` mit Konstante `GAME = "<key>"`, injiziert `GameScoreService`; `@Transactional(readOnly=true)` für Build/Check, `@Transactional` für `finish`. **Score wird ausschließlich serverseitig berechnet** (`gameScoreService.recordScore(user, GAME, deck, score, moves, seconds)`); Client sendet nur Roh-Antworten.
5. **Punkte-Konventionen der Plattform:** 1. Versuch volle Punkte, 2. Versuch ×0.6, danach ×0.3, aufgedeckte Lösung = 0; Sterne: Quote ≥0.8 → 3, ≥0.5 → 2, sonst 1; `MAX_ATTEMPTS = 3`.
6. **DTOs** in `dto/`: lösungsfreie View-DTOs, Request-DTOs, `*Result` (score, maxScore, solved, total, stars, highscore, newHighscore, plays).
7. **Controller:** `@Controller` (`GET /<spiel>` → Thymeleaf-View) + `@RestController @RequestMapping("/api/<spiel>")` mit `GET /session|board`, `POST /check` (mit `reveal`-Flag), `POST /finish`; `Principal` für den Usernamen.
8. **Seeder** (`CommandLineRunner` in `config/`, Guard `count() > 0`, liest `resources/<spiel>/content.json`).
9. **Admin-CRUD** `@Controller @RequestMapping("/admin/<spiel>")` mit Redirect-after-Post + Karte in `templates/admin/dashboard.html`.
10. **Frontend:** Template in `templates/`, eigenes CSS mit eindeutigem Präfix (nur `theme.css`-Tokens, Erfolg = blaues ✓, Fehler = rotes ✗, **nie grün**), JS als IIFE in `static/js/`, Highscore via `GET /api/games/<key>/stats`, Cache-Busting `?v=N`.
11. **Integration:** Route in `SecurityConfig`-CSRF-Ignore-Liste aufnehmen, Dashboard-Karte in `dashboard.html` von `/comingsoon` auf die echte Route umstellen. (`KNOWN_GAMES` + `ProgressService` enthalten die Keys bereits.)
---
## Teil 1: Spieldesigns
### Spiel 1: Code Escape Room (Key: `escape-room`, Migration V9)
**Konzept:** Der Spieler ist in einer digitalen Umgebung eingesperrt (Serverraum, Admin-Büro, Rechenzentrum). Ein **Szenario** = ein Durchlauf mit 3 Räumen in fester Reihenfolge; jeder Raum enthält 2–3 IT-Rätsel plus ein **Tür-Keypad**. Jedes gelöste Rätsel liefert ein **Code-Fragment** (Ziffern/Hex-Chunk in einer Inventar-Leiste); die Fragmente ergeben zusammen den Türcode zum nächsten Raum. Countdown erzeugt Zeitdruck; ein 2-stufiges Hinweis-System tauscht Punkte gegen Hilfe.
**Lernziele (AP1 → AP2):** Zahlensysteme (Bin/Hex/Dez), Ports & Protokolle, Subnetting, TCP-Handshake/Boot-Reihenfolgen, RAID, Hardware, SQL/Shell-Grundlagen – als Rätsel verpackt statt als Quiz. Schwere Szenarien tragen AP2-Stoff (Verschlüsselung/Zertifikate, Virtualisierung/Container, Backup-Strategien).
**Spielablauf:**
1. **Lobby:** Szenario-Karten (Titel, Thema, Schwierigkeit, Zeitlimit, persönliche Bestzeit).
2. **Raum-Ansicht:** Rätsel-Karten im Terminal-Look, Fragment-Inventar, Tür-Keypad. Rätsel öffnen sich im Modal.
3. **Rätsel-Versuch:** `MAX_ATTEMPTS = 3` wie Dev Lab; danach „Auflösen"-Button. Bei richtig **oder** aufgedeckt liefert der Server das Code-Fragment – Aufdecken blockiert nie den Fortschritt, es nullt nur die Punkte (Plattform-Konvention).
4. **Tür:** Spieler tippt den zusammengesetzten Code; Server validiert (`POST /door`). Falsch → rotes Blinken, keine Punktstrafe (die laufende Uhr ist die Strafe).
5. **Flucht/Ergebnis:** `POST /finish` rechnet alles serverseitig nach; Ergebnis-Screen mit Score-Aufschlüsselung (Rätselpunkte, Türboni, Hinweis-Abzug, Zeitbonus), Sternen, Highscore. „Aufgeben"-Button beendet früher (kein Zeitbonus, `escaped=false`).
**Punkteformel (serverseitig in `/finish` nachgerechnet):**
- Pro Rätsel: `punkte × versuchsfaktor × hinweisfaktor` (Versuch: 1.0/0.6/0.3, aufgedeckt 0; Hinweis: keiner ×1.0, Stufe 1 ×0.8, Stufe 2 ×0.6). Hinweis-Nutzung wird **serverseitig in der HttpSession** protokolliert (der `/hint`-Endpoint merkt sich die höchste gelieferte Stufe pro Rätsel) – der Client kann sie nicht unterschlagen. _(Einzige bewusste Abweichung vom sonst zustandslosen Muster.)_
- Pro geöffneter Tür: pauschal **+50**.
- Sterne aus `baseScore/maxScore` (≥0.8→3, ≥0.5→2). **Zeitbonus danach**, nur bei Flucht: `max(0, round(500 × (zeitlimit − sekunden) / zeitlimit))` – die MindMatch-Idee, auf 15-Minuten-Läufe skaliert. Timer auf 0 bricht nicht ab: HUD wechselt in „ALARM"-Zustand (pulsierendes `var(--danger)`), der Bonus ist nur weg.
- `recordScore(user, "escape-room", szenarioSlug, baseScore + zeitbonus, gelösteRätsel, sekunden)`.
**Datenmodell (`V9__escape_room.sql`):** 3 Tabellen im V8-Stil:
- `escape_scenarios` (id, slug UNIQUE, title, difficulty, description, time_limit_seconds INT DEFAULT 900, sort_order, active)
- `escape_rooms` (id, scenario_id FK CASCADE, title, intro, door_prompt, **door_code VARCHAR(50)**, success_text, sort_order)
- `escape_puzzles` (id, room_id FK CASCADE, puzzle_type VARCHAR(30), prompt, points INT DEFAULT 100, **code_fragment VARCHAR(20)**, sort_order, payload VARCHAR(8000))
- Indizes auf beide FKs. `code_fragment`/`door_code` als Spalten (nicht im Payload), damit Check/Door sie ohne JSON-Parsing haben und der Admin sie auf einen Blick sieht. Hinweise liegen im Payload als `"hints": [...]`.
**Backend:** Entities `EscapeScenario`/`EscapeRoom`/`EscapePuzzle` (Struktur wie `DevLabGame`/`DevLabTask`, LAZY, `addRoom`/`addPuzzle`-Helper). Repositories mit den Standard-Derived-Queries. `EscapeRoomService` mit `KNOWN_TYPES = Set.of("code","choice","order","match")`:
- `buildCatalog()` / `buildSession(slug)` – Szenario mit **allen** Räumen, Rätsel per `sanitize()` lösungsfrei (keine Lösungen, Fragmente, Hints, Türcodes). Raum-Sperre ist Client-Gate, abgesichert durch Server-Türvalidierung.
- `check(puzzleId, reveal, submission)` – Typ `code`: neuer trivialer Checker via `anyEquals(payload.solutions, answer, caseSensitive)`; **`choice`/`order`/`match`: Checker 1:1 aus `DevLabService` kopieren.** Bei `correct || reveal` kommt `"fragment"` in die Antwort.
- `openDoor(roomId, code)`, `hint(session, puzzleId, level)`, `finish(...)` wie oben; plus Admin-Methoden nach Dev-Lab-Vorbild.
**API (`/api/escape-room`):** `GET /scenarios`, `GET /session/{slug}`, `POST /check` (`{puzzleId, reveal, submission:{answer}}` → `{correct, fragment, successText}`), `POST /door` (`{roomId, code}` → `{open, successText}`), `POST /hint` (`{puzzleId, level}` → `{text, level}`), `POST /finish` (`{slug, durationSeconds, escaped, items:[{puzzleId, submission, attempts, revealed}], doors:[{roomId, code}]}` → `{score, baseScore, maxScore, timeBonus, solvedPuzzles, roomsOpened, escaped, stars, highscore, newHighscore, plays}`).
**Frontend:** `templates/escape-room.html`, `static/js/escaperoom.js` (IIFE-State-Machine `LOBBY → INTRO → ROOM(i) → PUZZLE_MODAL → DOOR_TRY → TRANSITION → … → RESULT`), `static/css/escaperoom.css` mit Präfix `esc-`. **Die `choice`/`order`/`match`-Renderer aus `devlabgame.js` (Vertrag `{render, collect, lock, mark}`) portieren**, nur ein `code`-Renderer (Terminal-Artefakt + Textfeld) kommt neu dazu. Layout: HUD oben (Raumtitel, Raum-Punkte ● ● ○, Timer-Chip), Mitte Rätselkarten, unten Fragment-Chips + Keypad-Button. Nur Theme-Tokens, Erfolg blau ✓, Fehler rot ✗.
**Content & Seeder:** `EscapeRoomSeeder` liest `resources/escaperoom/content.json`. Startumfang: **3 Szenarien** (leicht/mittel/schwer) × 3 Räume × 2–3 Rätsel ≈ 22–25 Rätsel. Themen: „Der verlassene Serverraum" (Netzwerk/Zahlensysteme), „Lockdown im Rechenzentrum" (Hardware/RAID/USV), „Das gehackte Büro" (Security/Passwörter/Verschlüsselung). Beispiel-Rätsel (ausgearbeitet im Design): PIN = Dezimalwert von `0b101001` → „41", SSH-Port-Choice, Dienst↔Port-Matching (Türcode „427"), TCP-Handshake-Reihenfolge, nutzbare Hosts im /26-Netz → „62" (Tresorcode „3E62").
**Admin:** `AdminEscapeRoomController` (`/admin/escape-room`), 3 Drilldown-Templates (Szenario-Liste → Szenario mit Räumen → Raum mit Rätseln; Rätsel-Payload als JSON-Textarea wie Dev Lab).
**Aufwand:** MVP ≈ 5,5 PT (Migration/Entities 0,5 · Service 1,5 · API 0,5 · Frontend 2 · Content 0,5 · Admin+Integration 0,5). Ausbau 1 (+1,5 PT): Bestzeiten in der Lobby, „Perfekter Lauf"-Badge. Ausbau 2 (+1–2 PT): Rätseltypen `regex`/`cloze` (Checker existieren wortgleich in `DevLabService`), 2 weitere Szenarien.
---
### Spiel 2: Bug Hunter (Key: `bug-hunter`, Migration V10)
**Konzept:** Der Spieler ist Debugging-Detektiv und arbeitet **Fallakten** ab. Ein Fall („Hunt") ist ein Mini-Projekt aus 2–4 Dateien, das sich falsch verhält; Beweismittel sind simulierte Konsolen-Ausgabe / Stacktrace / Ticket-Text. Pro Bug eine Drei-Phasen-Schleife: **ORTEN** (fehlerhafte Zeile im Tab-Code-Viewer anklicken) → **KLASSIFIZIEREN** (Syntax / Logik / Laufzeit / Sicherheit) → **FIXEN** (korrigierte Zeile aus 4 Kandidaten wählen). Saubere Bugs bauen eine **Combo-Serie** auf. Stufen: Junior / Senior / Principal.
**Abgrenzung zu Dev Lab:** Dev Labs `spot`-Typ ist „finde die eine falsche Zeile". Bug Hunter ist bewusst reicher: Multi-Datei-Navigation, beweisgetriebenes Denken (Stacktrace lesen → Zeile zuordnen), explizite Fehler-Taxonomie (Prüfungsthema!) und Fix-Auswahl – das vollständige Debugging-Denkmodell. Junior-Fälle decken AP1-Stoff ab; Senior/Principal-Fälle gehen auf FIAE-AP2-Niveau (Nebenläufigkeit/Threads, Transaktions-/SQL-Fehler, Design-Pattern-Missbrauch, OWASP-Sicherheitslücken).
**Punkteformel (serverseitig in `/finish`):**
- Phasengewichte pro Bug: Orten 40 %, Klassifizieren 20 %, Fixen 40 %. Phasen-Award = `gewicht × punkte × versuchsfaktor` (1.0/0.6/0.3, aufgedeckt 0).
- Sterne aus `baseScore/maxScore` (Genauigkeitsmaß, ohne Combo).
- **Combo:** Ein Bug ist _sauber_, wenn alle 3 Phasen im 1. Versuch ohne Aufdecken gelingen. Serienposition n bringt `(min(n,4) − 1) × 10 % × punkte` Bonus (2. saubere in Folge +10 %, … 4.+ +30 %); Bonus wird **nach** der Sternberechnung addiert. Kein Timer – Bug Hunter belohnt Präzision, Zeitdruck gehört dem Escape Room.
- Basispunkte pro Bug nach Stufe: Junior 100, Senior 150, Principal 200. `recordScore(user, "bug-hunter", huntSlug, baseScore + comboBonus, gelösteBugs, sekunden)`.
**Datenmodell (`V10__bug_hunter.sql`):**
- `bughunter_hunts` (id, slug UNIQUE, title, code_language, difficulty DEFAULT 'junior', description, briefing VARCHAR(2000), console_output VARCHAR(4000), sort_order, active)
- `bughunter_files` (id, hunt_id FK CASCADE, filename, content VARCHAR(8000), sort_order) – eigene Tabelle, damit der Code-Viewer-Inhalt von allen Bugs geteilt wird und im Admin als Textarea editierbar ist
- `bughunter_bugs` (id, hunt_id FK CASCADE, points DEFAULT 100, sort_order, payload VARCHAR(8000)) – Payload: `{"file","line","category","evidence","fixOptions":[4],"fixIndex","explanation"}`
- Indizes auf beide FKs.
**Backend:** Entities/Repos nach Dev-Lab-Muster. `BugHunterService` mit `CATEGORIES = Set.of("syntax","logik","laufzeit","sicherheit")`, Gewichten `0.4/0.2/0.4`:
- `buildSession(slug)`: Briefing + `consoleOutput` + Dateien als `[{filename, lines:[…]}]` (**serverseitig in Zeilen gesplittet**, damit Zeilenindizes kanonisch sind) + Bugs sanitisiert: nur `{bugId, points, evidence}` – **keine Zeile, keine Kategorie, keine Fix-Optionen**.
- `checkPhase(bugId, phase, reveal, submission)`: `locate` vergleicht file+line, liefert bei Erfolg/Aufdecken `location` zurück; `classify` vergleicht die Kategorie und liefert bei Auflösung erst die **gemischten `fixOptions`** (Phasen-Gate: der Fix-Text würde sonst die Position verraten); `fix` prüft die Wahl (Logik = Dev Labs `checkChoice`) und liefert `fixedLine` + `explanation` fürs Debriefing.
- `finish(...)`: prüft alle Phasen aus den Roh-Antworten neu, Gewichte + Versuchsfaktoren, Streak/Combo, Sterne, `recordScore`.
**API (`/api/bug-hunter`):** `GET /hunts`, `GET /session/{slug}`, `POST /check` (`{bugId, phase, reveal, submission}`), `POST /finish` (`{slug, durationSeconds, items:[{bugId, locate:{file,line,attempts,revealed}, classify:{category,…}, fix:{choice,…}}]}` → `{score, baseScore, maxScore, comboBonus, maxCombo, bugsSolved, totalBugs, stars, highscore, newHighscore, plays}`).
**Frontend:** `templates/bug-hunter.html`, `static/js/bughunter.js` (State-Machine `LOBBY → BRIEFING → HUNT {pro Bug: LOCATE → CLASSIFY → FIX → DEBRIEF} → RESULT`), `static/css/bughunter.css` Präfix `bh-`. Layout: **links 60 %** Datei-Tabs + Monospace-Code-Viewer mit Zeilennummern-Gutter (Zeilenklick nur in LOCATE aktiv); **rechts 40 %** Beweis-Karte (Terminal-Style `consoleOutput`, Briefing, Bug-Hinweis), Aktions-Panel (wechselt pro Phase; Kategorie-/Fix-Buttons nutzen das `choice`-Renderer-Muster aus `devlabgame.js`), Combo-Chip + Fortschritt („Bug 2/3"). Debriefing tauscht die Bug-Zeile sichtbar gegen `fixedLine` (blaues Diff-Highlight).
**Content & Seeder:** `BugHunterSeeder` liest `resources/bughunter/content.json`; der komplette Bug-Knoten wird wie bei `DevLabSeeder` per `toString()` zum Payload. Startumfang: **6 Fälle** – 3 Junior (1 Datei, 1–2 Bugs: Off-by-one, `=` vs. `==`, fehlendes `break`), 2 Senior (2 Dateien: NPE via Stacktrace, SQL-Injection, Integer-Division), 1 Principal (3 Dateien, Symptom in Datei A, Ursache in Datei B) ≈ 13–15 Bugs. Drei Beispiel-Fälle sind im Design voll ausgearbeitet („Fall #1: Die falsche Rechnung" – Off-by-one in Java; „Fall #2: Das offene Login" – SQL-Injection in PHP; „Fall #3: Der Absturz um Mitternacht" – NPE mit Stacktrace-Lesen), inkl. Code, Fix-Optionen und Erklärungen.
**Admin:** `AdminBugHunterController` (`/admin/bug-hunter`), 2 Templates (Fall-Liste + Fall-Detail mit Datei-Textareas und Bug-Payload-JSON). Service validiert beim Speichern, dass `file` im Fall existiert und `line` im Zeilenbereich liegt.
**Aufwand:** MVP ≈ 4,5 PT. Ausbau 1 (+1 PT): Freitext-Fix statt Optionen (`anyEquals` mit Whitespace-Normalisierung wie Dev-Lab-`cloze`), „Sauber gelöst"-Badge. Ausbau 2 (+1 PT): „Blitzjagd"-Tagesmodus (3 zufällige Junior-Bugs, 90-s-Soft-Timer).
---
### Spiel 3: Service Clash (Key: `service-clash`, Migration V11)
**Konzept: „Deine Schicht am Service Desk."** Der Spieler ist Junior-Admin im First-Level-Support. Tickets treffen auf einer Zeitleiste ein; jedes muss **eingeordnet** (Incident vs. Service Request), **priorisiert** (Hoch/Mittel/Niedrig anhand von Impact-×-Dringlichkeit-Hinweisen im Tickettext) und **gelöst** werden (richtige Erstmaßnahme aus 3–4 Optionen) – bevor sein SLA-Timer abläuft. Unbehandelte Tickets beschädigen die „Systemgesundheit"; bei 0 endet die Schicht vorzeitig.
**Lernziele (AP1 → AP2):** Incident-/Service-Request-Klassifikation (ITIL-light, explizites IHK-Thema), Prioritätsbewertung, korrekte Erstmaßnahmen (Troubleshooting-Reihenfolge, Eskalationsdisziplin, Phishing erkennen statt „lösen"). Höhere Schichten bringen FISI-AP2-Stoff: Change Management, Monitoring-/Eskalationsketten, Security-Incident-Behandlung, Backup-/Restore-Entscheidungen.
**Spielablauf:** Eine **Schicht** = ein Level mit 3 Wellen (Vormittag/Mittag/Nachmittag), je 60–90 Echtzeit-Sekunden mit steigender Spawn-Rate; Gesamtspielzeit 3–5 Minuten, zwischen Wellen 5 s Verschnaufpause. Pro Ticket im Detail-Panel:
1. **Einordnung** [Incident]/[Service Request] – Single-Shot, falsch = 0 Punkte für diesen Anteil, richtige Antwort wird sofort gezeigt (Lerneffekt). 20 % der Ticket-Basispunkte.
2. **Priorität** [Hoch]/[Mittel]/[Niedrig] – Single-Shot. 30 %.
3. **Lösung** – 3–4 Aktionen (servergemischt), Plattform-Versuchsleiter 1.0/0.6/0.3; nach dem 3. Fehlgriff Auflösung + Erklärung, Ticket zählt als „eskaliert" (0 für diesen Anteil). 50 %.
**SLA & Gesundheit:** `slaSeconds` je Priorität. In SLA gelöst → ×1.0, verspätet → ×0.5, nie gelöst → 0 + Gesundheitsschaden. Start 100; SLA-Bruch −10 (Hoch: −20), ungelöst am Schichtende −15. Endbonus: `gesundheit × 2` (max. 200).
**Punkteformel (nur serverseitig):**
```
ticketPunkte = B × (0.2·einordnungOk + 0.3·prioOk + 0.5·lösungsLeiter) × slaFaktor
score = Σ ticketPunkte + gesundheit×2 (B: Routine 60, Standard 100, Major Incident 150)
maxScore = Σ B + 200 ; Sterne nach Plattform-Konvention
```
**Der Server-autoritäts-Trick für Echtzeit (Kernidee):** Runden bleiben zustandslos –
- `GET /session/{slug}`: Server zieht einen **Seed**, generiert daraus deterministisch den Schichtplan (Ticket-Auswahl aus dem Pool, Spawn-Zeitpunkte, Options-Mischung; Payloads sanitisiert ohne Lösungsfelder) und liefert Plan + Seed.
- Jeder Klick geht an `POST /check` (sofortiges ✓/✗-Feedback; Lösungen verlassen den Server nie).
- `POST /finish` sendet `{slug, seed, decisions[], durationSeconds}`; der Server **regeneriert die Schicht aus dem Seed und re-simuliert das Entscheidungslog** (Plausibilitätschecks: Zeitstempel ≥ Spawn-Offset, ≤ Schichtdauer, monoton pro Ticket) und berechnet Gesundheit, SLA-Faktoren und Score neu. Härtungs-Ausbau: HMAC-signierter Seed.
**Datenmodell (`V11__service_clash.sql`):**
- `service_clash_shifts` (id, slug UNIQUE, title, description, difficulty INT, wave_count INT DEFAULT 3, wave_seconds INT DEFAULT 60, spawn_count INT DEFAULT 12, settings VARCHAR(4000) /* JSON: SLA-Defaults, Health-Regeln, Prio-Stufe an/aus */, sort_order, active)
- `service_clash_tickets` (id, shift_id FK CASCADE, wave INT, base_points INT DEFAULT 100, sort_order, payload VARCHAR(8000)) + FK-Index
- Ticket-Payload: `{title, requester, text, kind (normal|phishing), options[], correctIndex✂, category✂, priority✂, slaSeconds, explanation✂, successText}` (✂ = wird von `sanitize()` entfernt).
**Backend:** Entities `ServiceClashShift`/`ServiceClashTicket`, Standard-Repos (`findAllByShiftIdOrderByWaveAscSortOrderAscIdAsc`, `countByShiftId`). `ServiceClashService`: `buildCatalog()`, `buildSession(slug)` (Seed + `generatePlan()`), `check(templateId, step, value, reveal)` mit `step ∈ classify|priority|solve` (Vergleich immer über Options-**Text**, nie Indizes – immun gegen Mischungs-Versatz), `finish(...)` (Plan aus Seed regenerieren, Log re-simulieren, `recordScore(user, "service-clash", slug, score, gelöste, sekunden)`), Admin-Methoden nach Dev-Lab-Vorbild.
**API (`/api/service-clash`):** `GET /catalog`, `GET /session/{slug}` (liefert `seed`, Wellen-Konfiguration, Ticket-Instanzen `{instanceId, templateId, wave, spawnAtMs, …}`), `POST /check`, `POST /finish` → `{score, maxScore, stars, resolved, total, slaBreaches, health, highscore, newHighscore, plays, tickets[Debrief-Aufschlüsselung]}`.
**Frontend:** `templates/service-clash.html`, `static/js/serviceclash.js` (State-Machine `SHIFT_SELECT → BRIEFING → RUNNING ⇄ WAVE_BREAK → DEBRIEF`; **ein** `setInterval(250 ms)`-Tick: fällige Tickets spawnen, SLA-Ringe aktualisieren), `static/css/service-clash.css` Präfix `sc-`. Layout: Topbar (Schicht-Uhr, Wellen-Pips, Gesundheits-Meter, „Gelöst 4/8"), links die Ticket-Queue (Karten mit SLA-Ring, pulsierender roter Rand bei Überfälligkeit), Mitte das aktive Ticket mit 3-Stufen-Flow, unten ein Event-Log („14:02 Ticket ‚ERP down' eingegangen"). SLA-Ringe: `var(--accent)`, unter 25 % `var(--danger)`.
**Schwierigkeitskurve:** Schicht 1 „Erster Arbeitstag" (8 Tickets, großzügige SLAs, Prio-Stufe deaktiviert), Schicht 2 „Normalbetrieb" (12 Tickets, Prio aktiv, erster Phishing-Köder), Schicht 3 „Patch Day Chaos" (16 Tickets, knappe SLAs, Major Incident mit Folgetickets in Welle 3). **Replay-Wert:** Ticket-Pool größer als `spawnCount` → jede Runde ein anderer Mix (Seed).
**Content & Seeder:** `ServiceClashSeeder` liest `resources/service-clash/content.json`. Startumfang: **3 Schichten × 18–20 Ticket-Vorlagen ≈ 55 Tickets.** Vier Beispiel-Tickets sind voll ausgearbeitet (Drucker-Queue/Incident-mittel, Azubi-Onboarding/Service-Request-niedrig, ERP-Totalausfall/Major-Incident-hoch mit Sofort-Eskalation, Phishing-Mail mit verdächtiger Absenderdomain).
**Admin:** `AdminServiceClashController` (`/admin/service-clash`), Schicht-Liste + Detailseite (Schicht-Formular inkl. Settings-JSON, Ticket-Tabelle, Payload-JSON-Textarea mit Schema-Hinweis; Speichern validiert `correctIndex` gegen `options`).
**Aufwand:** MVP ≈ 7,5 PT (Service inkl. Seed-Plan + Re-Simulation 2 · Frontend 3 · Content 1 · Admin 1 · Rest 0,5+0,5). Ausbau (+3 PT): Major-Incident-Folgeketten, Combo-Serien, HMAC-Seed, Endlosmodus „Überstunden".
---
### Spiel 4: Netzwerker – Das Router-Rätselspiel (Key: `netzwerker`, Migration V12)
**Konzept:** Netzwerk-Puzzle in der Tradition von Schaltkreis-Spielen. Jedes Level zeigt eine **SVG-Topologie** (PCs, Switch, Router, Server, Cloud). Geräte haben fehlende oder kaputte Konfiguration (IP, Maske, Gateway, Kabel, statische Routen). Ziel: alle **Ping-Ziele** erfüllen („PC1 muss SRV1 erreichen", später auch negativ: „Gast-PC darf den Server NICHT erreichen"). „Pings testen" ruft einen **serverseitigen Netzwerk-Simulator**, der jeden Ping Hop-für-Hop auflöst und Pass/Fail plus didaktische Diagnose liefert („Das Paket verlässt PC1 nicht: Laut Maske liegt das Ziel im eigenen Netz – stimmt die Maske?").
**Lernziele (AP1-Kernstoff → AP2, passt zu vorhandenem FinalFlux-Content IP-Berechnung/VLAN):** IPv4-Adressierung, Subnetzmasken/CIDR, Netz-/Broadcast-Adressen, Gateway-Logik, doppelte IPs, statische Routen; auf AP2-Niveau (FISI): VLAN-Segmentierung mit Negativ-Zielen, Routing über mehrere Hops, Firewall-Regeln, im Ausbau IPv6.
**Spielablauf:** Session = eine **Welt** aus 4–6 Leveln in Folge (Dev-Lab-Session-Form). Fünf Level-Typen bei einheitlichem Datenmodell: `assign` (leere IP/Maske/Gateway-Felder ausfüllen), `cable` (fehlende Verbindungen ziehen), `fix` (1–3 eingebaute Fehler finden: falsche Maske, Gateway außerhalb des Subnetzes, doppelte IP, Host auf Netzadresse), `route` (statische Routen-Zeilen: Zielnetz-CIDR + Next Hop), `vlan` (Ausbau, mit Negativ-Zielen).
**Versuche & Punkte:** Jeder „Pings testen"-Klick mit mindestens einem roten Ziel verbraucht einen Versuch (1.0/0.6/0.3, `MAX_ATTEMPTS=3`, danach „Lösung anzeigen" = 0 Punkte). **Bewertet wird ausschließlich per Simulation, nie per Exact-Match** – viele Konfigurationen sind gültig (jede freie Host-IP funktioniert); die Musterlösung dient nur dem Aufdecken. Levelpunkte 100–200; Sterne am Weltende nach Konvention; `/finish` re-simuliert die final eingereichten Konfigurationen serverseitig. `recordScore(user, "netzwerker", weltSlug, score, gelöste, sekunden)`.
**Datenmodell (`V12__netzwerker.sql`):**
- `netzwerker_worlds` (id, slug UNIQUE, title, description, difficulty INT, sort_order, active)
- `netzwerker_levels` (id, world_id FK CASCADE, title, level_type VARCHAR(30) /* assign|cable|fix|route|vlan */, points INT DEFAULT 100, sort_order, payload VARCHAR(8000) /* JSON-Topologie inkl. Musterlösung */) + FK-Index
**Der Simulator – `service/NetSimulator.java` (Herzstück, pures Java ohne Spring, voll unit-testbar):**
- Records: `Net(nodes, links, routes, vlans)`, `NodeCfg`, `IfaceCfg(ipLong, prefix, gatewayLong)`, `Route(destNet, prefix, nextHop)`.
- `ping(net, from, to)`: (1) Validierung: unparsebare IP/Maske, Host-Teil 0/alles-1 → `network_address`/`broadcast_address`, doppelte IP im L2-Segment → `duplicate_ip`. (2) Gleiches Subnetz aus Quellsicht (`(ip & maske) == (ziel & maske)`) → L2-BFS über Links durch Switches (nie durch Router; VLAN-gefiltert). (3) Anderes Subnetz → Gateway nötig: gesetzt? im Quell-Subnetz? L2-erreichbar? Dann leitet der Router weiter: Ziel in direkt verbundenem Subnetz → zustellen; sonst statische Routen (Longest-Prefix-Match) → Next Hop → Rekursion mit TTL 8 (`routing_loop`-Schutz). Kein Treffer → `no_route`.
- Fehlergründe werden im Service auf didaktische deutsche Meldungen gemappt.
**`NetzwerkerService`:** `buildCatalog()`, `buildSession(slug)` (Level per `sanitizeLevel()`: Werte editierbarer Felder, zu ziehende Links, Routen-Lösungszeilen und `solution`-Block entfernt; gesperrte Werte, Positionen, Ziele, Hints gehen mit), `check(levelId, reveal, config)` (`mergeSubmission()` legt eingereichte Werte **nur über editierbare Felder** – Manipulation gesperrter Felder wird ignoriert; alle Ziele durch `NetSimulator.ping`; Antwort mit Trace/Diagnose pro Ziel), `finish(...)`, Admin-CRUD **plus Qualitäts-Gate beim Speichern: die hinterlegte Musterlösung wird durch den Simulator geschickt – Speichern wird abgelehnt, wenn sie nicht alle Ziele erfüllt.**
**API (`/api/netzwerker`):** `GET /catalog`, `GET /session/{slug}`, `POST /check` (`{levelId, reveal, config:{ifaces[], links[], routes[], vlans[]}}` → `{allPassed, validation[], goals:[{id, passed, trace[] | failAt, reason, message}]}`; Masken-Eingabe akzeptiert `24`, `/24` oder `255.255.255.0` – Server normalisiert), `POST /finish` → Dev-Lab-Result-Form.
**Frontend:** `templates/netzwerker.html`, `static/js/netzwerker.js` (State-Machine `WORLD_SELECT → LEVEL_INTRO → EDITING ⇄ CHECKING → LEVEL_DONE → SUMMARY`), `static/css/netzwerker.css` Präfix `nw-`. **SVG-Ansatz:** ein `<svg viewBox="0 0 1000 620">`, Geräte-Glyphen als `<symbol>`-Defs im Strichstil der Dashboard-Icons, Positionen aus dem Payload; Links als `<path>`, Ports als Kreise an der Gerätekante. **Die Paket-Animation ist die Belohnung:** bei Erfolg wandert ein leuchtender Punkt entlang des tatsächlich vom Server gelieferten `trace`-Pfads (`requestAnimationFrame`); bei Fehlschlag stirbt er exakt am `failAt`-Hop mit rotem Burst. Kabelmodus: Port A klicken → Gummiband-Linie → Port B; gezogenes Kabel per Klick löschen. Layout: Canvas links/Mitte, rechts Ziel-Liste (mit Diagnosetexten unter roten Zielen) + Konfigurations-Panel des gewählten Geräts, Footer mit „Pings testen" / „Lösung anzeigen" / Hinweis-Toggle.
**Schwierigkeitskurve (Welten):** 1. „Ein Netz für alle" (Same-Subnet-Basics), 2. „Über den Router" (Gateways, zwei Subnetze, erstes Negativ-Ziel), 3. „Routen & Fehlersuche" (zwei Router, statische Routen, Duplikat-IP-Jagd), 4. (Ausbau) „VLAN-Werkstatt".
**Content & Seeder:** `NetzwerkerSeeder` liest `resources/netzwerker/content.json`. Startumfang: **3 Welten × 5 Level = 15 Level.** Drei Beispiel-Level voll ausgearbeitet: „Erste Verbindung" (assign: PC1 passend zu 192.168.10.20/24 konfigurieren), „Die falsche Maske" (fix: /25-vs-/24-Konflikt, .200 liegt außerhalb des ersten /25-Teilnetzes), „Tor zum Serverraum" (assign: IP+Maske+Gateway über Router zwischen 192.168.1.0/24 und 192.168.2.0/24).
**Admin:** `AdminNetzwerkerController` (`/admin/netzwerker`), Welt-Liste + Detailseite mit Level-Payload-JSON-Textarea (eingeklapptes Schema-Cheatsheet) und dem Musterlösungs-Validierungs-Gate.
**Aufwand:** MVP ≈ 9,5 PT (**NetSimulator + Unit-Tests zuerst: 2,5** · Migration/Entities/Service/API 1,5 · SVG-Frontend 3,5 · Content 1 · Admin 1). Ausbau (+4,5 PT): VLAN-Welt, DHCP-Modus, Sandbox, visueller Level-Editor.
---
### Empfohlene Baureihenfolge & Gesamtaufwand
0. **Vorarbeiten:** Namensschema umsetzen (Dashboard-Namen, Keys, `devlab` in `ProgressService` = W1) · K2 (Legacy-Endpunkt löschen) und W8 (N+1-Muster) beheben, damit die neuen Services das saubere Muster übernehmen · Spiel-Akzent-Tokens in `theme.css` anlegen. ≈ 1 PT — **✅ erledigt bis auf die Spiel-Akzent-Tokens.**
1. **CodeEscape (V9)** – validiert die Wiederverwendung der DevLab-Checker/-Renderer mit minimalem Risiko. ≈ 5,5 PT
2. **BugHunter (V10)** – übernimmt Terminal-Styling und Choice-Renderer aus Schritt 1. ≈ 4,5 PT
3. **NetWorker (V12)** – **Simulator zuerst, testgetrieben** (pures Java, kein UI-Risiko), dann SVG-Frontend. ≈ 9,5 PT
4. **ServiceClash (V11)** – komplexestes Frontend (Echtzeit-Tick); profitiert von allen Vorarbeiten. ≈ 7,5 PT
**Gesamt-MVP aller vier Spiele: ≈ 28 Personentage.** Der Navigations-Umbau (Abschnitt 2.3) und die restlichen Teil-2-Punkte können parallel oder dazwischen erfolgen; empfohlen ist der Sidebar-/Tab-Umbau **vor** dem vierten Spiel, damit alle neuen Seiten die Fragmente nutzen.
---
## Teil 2: Verbesserungen & Optimierungen am Bestand
> ✅ **Umgesetzt am 2026-07-12** (K1–K3, W1–W8, N1–N6, S1–S8, S10 – Details im CHANGELOG). Offen bleiben: S9/i18n (bewusst zurückgestellt), S3 (Regel für die neuen Spiele) sowie die schrittweise Fragment-Migration der restlichen Spiel-/Admin-Templates. **K1 vollständig abgeschlossen:** Gitea-Secret angelegt, Produktionspasswort rotiert und Runner-Abgleich erfolgreich deployt (Workflow 43, Commit `4f5042d`). Das Verfahren und der manuelle Notfallweg stehen in [`pw_anleitung.md`](pw_anleitung.md).
### 2.1 Kritisch (vor/mit dem nächsten Release beheben)
| # | Problem | Ort | Maßnahme |
|---|---------|-----|----------|
| K1 ✅ | **DB-Passwort im Repo** (`SPRING_DATASOURCE_PASSWORD: mindflux_pass`) | `docker-compose.yml:11` | In Gitea-Secret auslagern (wie die Admin-Passwörter bereits); Passwort rotieren. |
| K2 ✅ | **Unsicherer Legacy-Endpunkt `/users/save`**: speichert User ohne BCrypt-Hashing, ohne Validierung, mit frei wählbarer Rolle (Mass Assignment) – für jeden eingeloggten Nutzer erreichbar | `UserViewController.java:25–29` | `UserViewController` + `user-form.html` + `user-success.html` ersatzlos löschen (toter Legacy-Code). |
| K3 ✅ | **Keine Tests** – `src/test` existiert nicht, obwohl `spring-boot-starter-test` deklariert ist | `pom.xml:88–93` | Mindestens: Unit-Tests für Score-Berechnungen (`GameScoreService`, `SyntaxSagaService`, `DevLabService.check`-Dispatcher) und einen `@WebMvcTest` für die Security-Regeln (`/admin/**` nur ADMIN). Die Netzwerk-Simulation des neuen Netzwerker-Spiels unbedingt testgetrieben bauen. |
### 2.2 Wichtig (zeitnah)
| # | Problem | Ort | Maßnahme |
|---|---------|-----|----------|
| W1 ✅ | **Dev Lab fehlt auf der Statistik-Seite** – Scores werden geschrieben, aber nie angezeigt | `ProgressService.GAMES` (Zeile 18–27) | `GAMES.put("devlab", "Dev Lab");` ergänzen (Reihenfolge wie Dashboard). |
| W2 ✅ | **XP/Level/Streak sind Dummy-Daten** (`getOverallProgress` hardcoded 432/2400/Level 5) | `ProgressService.java:35–44` | Echte Aggregation aus `game_scores`: XP = Summe aller Scores (oder gedeckelt pro Spiel), Level per Schwellen-Tabelle, richtigen Antworten aus `moves` ableiten. Gleiches Fundament später für Achievements nutzen. |
| W3 ✅ | **Achievements sind Dummy-Daten**, nicht mit `game_scores` verdrahtet | `AchievementServiceImpl` | Nach `recordScore()` einen Achievement-Check auslösen (z. B. „Erster Sieg", „3 Sterne in Syntax Saga", „5 Spiele an einem Tag"). Ereignisgesteuert im `GameScoreService` (ein Hook-Punkt statt in jedem Spiel). |
| W4 ✅ | **Kein globales Error-Handling** – Admin-Controller werfen `IllegalArgumentException` → Whitelabel-500; APIs liefern leere DTOs statt 404 | alle Controller | Ein `@ControllerAdvice` (HTML: eigene `error.html`) + `@RestControllerAdvice` (JSON: `{error, message}` mit korrekten Statuscodes). `ResponseStatusException` in Services nutzen. |
| W5 ✅ | **Keine Bean Validation** – kein `spring-boot-starter-validation`, keine `@Valid`/`@NotBlank`/`@Size`; keine Passwort-Mindestlänge, kein E-Mail-Formatcheck | `pom.xml`, `RegisterController.java:34–42`, `User.java` | Starter ergänzen; DTO-Validierung für Registrierung (Passwort ≥ 8 Zeichen, E-Mail-Format) und die neuen Spiel-APIs. |
| W6 ✅ | **Registrierungs-Race + tote Templates**: App prüft Username case-insensitiv, DB-UNIQUE ist case-sensitiv → Race mit unbehandeltem 500; `register.html` (ohne `confirmPassword`-Feld) und `login.html` sind tot/kaputt | `RegisterController.java:40`, `V1__initial_schema.sql` | Funktionalen Index `UNIQUE (lower(username))` per Migration; `DataIntegrityViolationException` abfangen → Fehlermeldung; tote Templates `register.html`/`login.html` löschen. |
| W7 ✅ | **`System.out.println` / `printStackTrace` statt Logging** (~20 Stellen, u. a. `ProfileController.java:144`) | Seeder, `AdminSetup`, `ProfileController`, `AppStartCheck` | Auf SLF4J (`LoggerFactory.getLogger`) umstellen; `e.printStackTrace()` durch `log.error(..., e)` ersetzen. |
| W8 ✅ | **N+1-Queries**: `TechtacularService.buildBoard` lädt Fragen+Antworten lazy pro Kategorie; `categoryRows` macht ein `count` pro Kategorie | `TechtacularService.java:46–73, 133–137` | `@EntityGraph`/`JOIN FETCH` für Board-Aufbau; Zeilen-Counts als eine `GROUP BY`-Query. Bei Seed-Datenmengen unkritisch, aber das Muster wird sonst in die 4 neuen Spiele kopiert – **daher vor deren Bau beheben bzw. in den neuen Services von Anfang an richtig machen.** |
### 2.3 Navigation & Profil-Bereich (vom Nutzer gemeldet – bestätigt)
Der Verdacht „Profilbutton fehlt / Einstellungen nicht im Profil / irgendwas ist schief" trifft zu. Befund:
| # | Befund | Ort |
|---|--------|-----|
| N1 ✅ | **Kein „Profil"-Eintrag in der Dashboard-Sidebar-Navigation.** Das Profil ist nur über den kleinen User-Block unten in der Sidebar erreichbar (`<a class="mf-profile" href="/profile">`), der nicht als Button erkennbar ist – und dessen Chevron-Pfeil (▼) ein Dropdown suggeriert, das nicht existiert. | `dashboard.html:72–82` |
| N2 ✅ | **Zwei konkurrierende Navigationssysteme:** Die Sidebar führt zu Knowledge Core / Statistiken / Achievements / Einstellungen; die Profilseite hat eine **eigene Tab-Leiste** (Profil / Fortschritt / Erfolge / Soziales / Einstellungen). Benennungen inkonsistent („Statistiken" vs. „Fortschritt", „Achievements" vs. „Erfolge"), Mengen inkonsistent (Knowledge Core fehlt in den Tabs, Soziales fehlt in der Sidebar). | `dashboard.html:45–70` vs. `profile.html:35–41` |
| N3 ✅ | **Die Tab-Leiste existiert nur auf `profile.html`.** Klickt man einen „Tab" (z. B. Einstellungen), landet man auf einer Seite **ohne** Tabs – die Tab-Metapher bricht sofort. `settings.html` hat nur „⟵ Zurück zum Profil", auch wenn man von der Dashboard-Sidebar kam (asymmetrische Navigation). | Grep `mf-tab-nav`: nur `profile.html` (+ eine Admin-Seite) |
| N4 ✅ | **`/profile/settings` gehört nicht zum Profil-Code:** Die URL suggeriert Profil-Zugehörigkeit, wird aber von einem separaten `SettingsController` bedient; die Passwort-Änderung dagegen liegt im `ProfileController` (`/profile/change-password`) und redirectet nach `settings`. Zuständigkeiten sind verschränkt. | `SettingsController.java`, `ProfileController.java:154–177` |
| N5 ✅ | **`settings.html` ist zur Hälfte Attrappe:** Theme-Select (Hinweistext verweist auf den echten Umschalter), Sprach-Select und Benachrichtigungs-Checkboxen haben **keine Funktion/kein Formular**. Real sind nur Passwort ändern und Profil löschen. Zudem hartkodierte Farbe `#c0392b` statt `var(--danger)` (Token-Regel-Verstoß). | `settings.html:40–70, 37` |
| N6 ✅ | **Sidebar-Widgets sind Dummy-Daten:** LEVEL 12 / 3250/5000 XP / „STREAK 7 Tage" sind hart ins Template geschrieben – deckt sich mit W2 (XP/Level-Dummies im `ProgressService`). | `dashboard.html:84–91` |
**Lösungsansatz (ein zusammenhängender Umbau, verzahnt mit S1/Layout-Fragmenten):**
1. **Ein Navigationsmodell:** Die Sidebar wird als Thymeleaf-Fragment extrahiert und um einen expliziten Eintrag **„Profil" → `/profile`** ergänzt; der User-Block bleibt als Shortcut, verliert aber den irreführenden Chevron (oder bekommt ein echtes Dropdown mit Profil/Einstellungen/Logout).
2. **Profil-Bereich konsolidieren:** Die Tab-Leiste wird ebenfalls Fragment und erscheint **auf allen** Profil-Unterseiten (Profil, Statistiken, Erfolge, Soziales, Einstellungen) mit einheitlichen Namen (Empfehlung: „Statistiken" und „Erfolge" überall). Knowledge Core entweder in die Tabs aufnehmen oder bewusst nur in der Sidebar führen – einmal entscheiden.
3. **Controller aufräumen:** `SettingsController` in den `ProfileController` mergen (oder Settings vollständig auslagern inkl. Passwort-Endpunkt) – eine Zuständigkeit pro Bereich.
4. **Attrappen entfernen oder verdrahten:** Theme-Select an `theme.js` anbinden (Auswahl persistiert in `localStorage`, ersetzt den Hinweistext); Sprach-Select und Benachrichtigungen **ausbauen**, bis es die Funktionen gibt. `#c0392b` → `var(--danger)`.
5. N6 wird durch W2 (echte XP/Level aus `game_scores`) gelöst; bis dahin Widgets ausblenden statt Fantasiewerte zeigen.
### 2.4 Sinnvoll (mittelfristig)
| # | Thema | Maßnahme |
|---|-------|----------|
| S1 ✅ teilweise | **Template-Duplikation** – 33 Templates ohne ein einziges `th:fragment`; jede Seite dupliziert `<head>`, Sidebar, Footer | `templates/fragments/layout.html` mit `head`, `sidebar`, `footer`-Fragmenten einführen; schrittweise umstellen (zuerst bei den 4 neuen Spielen verwenden, dann Bestand migrieren). |
| S2 ✅ | **4 parallele User-Anlage-Pfade** mit inkonsistenten Regeln (`RegisterController`, REST-`UserController`, `AdminUserController`, `UserViewController`) | Nach Löschung von K2: Restlogik in einen `UserService` konsolidieren (ein Ort für Encoding, Rollen, Uniqueness). |
| S3 ☐ | **Score-Manipulation durch eigene Nutzer möglich** (Roh-Werte fälschbar, Reveal-vor-Finish) | Akzeptables Risiko (nur eigener Highscore), aber bei den neuen Spielen von Anfang an minimieren: Server generiert Runden-Daten mit IDs, `finish` validiert nur bekannte IDs, Zeit-Plausibilitätscheck (z. B. `durationSeconds ≥ 1`, Obergrenzen für `pairs`/`moves`). |
| S4 ✅ | **Infrastruktur**: kein Actuator/Healthcheck, Dockerfile läuft als root, kein Dependency-Layer-Caching, CI ohne Tests, Java-Versionskonflikt (pom: 17, Dockerfile: 21) | `spring-boot-starter-actuator` (+ `HEALTHCHECK` in compose), `USER`-Directive, Dockerfile zweistufig mit `dependency:go-offline`-Layer, Test-Stage in `.gitea/workflows/mindflux.yml`, Java-Version angleichen (empfohlen: beide auf 21). |
| S5 ✅ | **Datenmodell**: `game_scores`/`achievements` referenzieren `username` ohne FK; `email` ohne UNIQUE | FK auf `users(username)` + `UNIQUE(email)` per Migration ergänzen (vorher Dubletten bereinigen). |
| S6 ✅ | **Logout per GET** (`SecurityConfig.java:36`) – kleiner Logout-CSRF-Vektor | Auf POST-Logout mit CSRF-Token umstellen (kleines Formular im Sidebar-Footer). |
| S7 ✅ | **Kein Brute-Force-Schutz** auf `/login` | Einfacher In-Memory-Rate-Limiter (z. B. Bucket pro IP/Username) oder Verzögerung nach Fehlversuchen. |
| S8 ✅ | **Toter Code**: `AppStartCheck`, `ProfileController.getFileExtension`, Legacy-CSS (`auth.css`, …) | Aufräumen (niedrige Priorität, aber billig). |
| S9 ☐ | **i18n**: alles hart deutsch | Nur angehen, falls Mehrsprachigkeit je geplant ist – sonst bewusst so lassen (Zielgruppe ist deutsch). |
| S10 ✅ | **FinalFlux auf die gesamte Ausbildung erweitern (AP2)** – aktuell rein AP1-fokussiert | Eigener AP2-Ast mit Sidebar-Blöcken „AP2 Anwendungsentwicklung" und „AP2 Systemintegration"; gleiche Struktur wie bisher (statische Themenseiten unter `static/finalflux/` + FluxCheck-Frage-JSONs). Startthemen FIAE: Entwurfsmuster, Testverfahren, SQL-Vertiefung, Softwarequalität; FISI: Netzwerksicherheit, Monitoring, Hochverfügbarkeit, Automatisierung. |
---
## Teil 3: FluxNet – Community & Soziales
> Ersetzt den `/soziales`-Platzhalter („Straßen? Wo wir hingehen, brauchen wir keine Straßen!"). Ziel: User können sich befreunden, intern miteinander schreiben, gemeinsam lernen und Achievements vergleichen – **vollständig selbst gehostet, ohne einen einzigen externen Dienst**, und DSGVO-konform von Grund auf.
### 3.1 Funktionsumfang
| Funktion | Beschreibung |
|----------|--------------|
| **Freundschaften** | Anfrage per Username-Suche senden → Gegenseite bestätigt/lehnt ab. Nur bestätigte Freunde sehen einander im FluxNet. |
| **Interner Messenger** | 1:1-Direktnachrichten zwischen bestätigten Freunden. Ungelesen-Badge am Glocken-Icon der Topbar. Kein Gruppenchat in Phase 1. |
| **Blockieren** | Blockiert = keine Anfragen, keine Nachrichten, keine Profileinsicht – in beide Richtungen serverseitig geprüft. Bestehende Freundschaft wird getrennt. Der Blockierte erhält **keine** Benachrichtigung (still). Entblocken jederzeit über die Einstellungen. |
| **Achievements vergleichen** | Freundesprofil zeigt den Achievement-Katalog nebeneinander (meine/seine freigeschalteten) und die Spiel-Highscores im Vergleich – nur, was die Privacy-Einstellungen des anderen erlauben. |
| **Gemeinsam lernen** | Phase 2: **Lern-Duell** – einen Freund zu Spiel + Deck herausfordern, beide spielen binnen Frist, der Server vergleicht die echten `game_scores`-Ergebnisse. Phase 3: gemeinsame Lernräume (identisches Board für beide über die Seed-Mechanik aus dem ServiceClash-Design). |
### 3.2 Datenmodell (nächste freie Flyway-Migrationen)
Alle Tabellen mit FK auf `users(username)` und `ON DELETE CASCADE ON UPDATE CASCADE` (Muster aus V10) – damit greift das Löschkonzept automatisch.
```sql
CREATE TABLE privacy_settings (
username VARCHAR(100) PRIMARY KEY REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
community_enabled BOOLEAN NOT NULL DEFAULT FALSE, -- DSGVO: Opt-in!
profile_visibility VARCHAR(20) NOT NULL DEFAULT 'FRIENDS', -- FRIENDS | ALL
show_achievements BOOLEAN NOT NULL DEFAULT TRUE,
show_stats BOOLEAN NOT NULL DEFAULT TRUE,
allow_requests BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE friendships (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
requester VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
addressee VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING | ACCEPTED
created_at TIMESTAMP NOT NULL,
responded_at TIMESTAMP,
CONSTRAINT uq_friend_pair UNIQUE (requester, addressee),
CONSTRAINT chk_no_self CHECK (requester <> addressee)
);
CREATE TABLE user_blocks (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
blocker VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
blocked VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
created_at TIMESTAMP NOT NULL,
CONSTRAINT uq_block_pair UNIQUE (blocker, blocked)
);
CREATE TABLE messages (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
sender VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
recipient VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
body VARCHAR(2000) NOT NULL,
sent_at TIMESTAMP NOT NULL,
read_at TIMESTAMP,
reported BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX idx_messages_unread ON messages (recipient, read_at);
CREATE INDEX idx_messages_pair ON messages (sender, recipient, sent_at);
CREATE TABLE challenges (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
challenger VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
challenged VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
game VARCHAR(50) NOT NULL,
deck VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'OPEN', -- OPEN | DONE | EXPIRED | DECLINED
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL
);
```
Eine Freundschaft zwischen A und B liegt genau einmal vor (Anfrage-Richtung = requester); Abfragen prüfen beide Richtungen (`(requester=?1 AND addressee=?2) OR (requester=?2 AND addressee=?1)`).
### 3.3 Backend (bewährtes Schichtmuster)
- **Services:** `FriendshipService` (anfragen/annehmen/ablehnen/entfernen), `MessageService` (senden, Verlauf paginiert laden, als gelesen markieren, eigene Nachricht löschen, melden), `BlockService` (blocken/entblocken, `isBlockedEitherWay(a, b)`), `PrivacyService` (Opt-in, Sichtbarkeiten).
- **Zentrale Guard-Methode**, die JEDE soziale Aktion durchläuft: Community aktiviert (beide Seiten)? Block in irgendeiner Richtung? Freundschaft bestätigt (für Nachrichten/Profile)? → sonst 403/404, ohne dem Angreifer zu verraten, ob der Zielnutzer existiert oder blockt.
- **Controller:** `SozialesController` (View `/soziales`, Freundesprofil `/soziales/profil/{username}`) + `@RestController /api/social/**` (Endpunkte: `GET /friends`, `POST /friends/request|accept|decline|remove`, `GET /messages/{friend}?after=`, `POST /messages`, `POST /messages/{id}/report`, `DELETE /messages/{id}`, `GET /unread-count`, `POST /block`, `DELETE /block/{username}`, `GET/PUT /privacy`). Route in die CSRF-Ignore-Liste der `SecurityConfig` aufnehmen (Muster der Spiel-APIs).
- **Moderation:** gemeldete Nachrichten landen in einer Admin-Ansicht (`/admin/fluxnet`) – der Admin sieht **ausschließlich gemeldete** Nachrichten (Verhältnismäßigkeit), kann löschen und Nutzer verwarnen/sperren.
### 3.4 Messenger-Transport: voll intern, keine externen Dienste
- **Phase 1 – Polling:** offener Chat pollt alle 5 s `GET /api/social/messages/{friend}?after=<letzteId>`, das Ungelesen-Badge alle 30 s `GET /api/social/unread-count`. Passt exakt zur zustandslosen Vanilla-JS-Architektur, braucht null neue Infrastruktur und skaliert für die Zielgruppe locker.
- **Phase 2 – Server-Sent Events:** `GET /api/social/stream` mit Spring `SseEmitter` (im `MessageService` wird bei `send()` an offene Emitter des Empfängers publiziert). Weiterhin 100 % selbst gehostet. nginx: `proxy_buffering off;` + langes `proxy_read_timeout` für den Stream-Pfad.
- **Bewusst NICHT:** WebSocket/STOMP (Overkill für 1:1-Chat) und Web-Push/FCM/Service-Worker-Push – Letzteres liefe zwingend über die Push-Infrastruktur der Browserhersteller (Google/Mozilla/Apple) und wäre damit ein externer Dienst.
### 3.5 DSGVO-Konzept
| Prinzip | Umsetzung |
|---------|-----------|
| **Opt-in (Art. 6, 25)** | `community_enabled = FALSE` als Default. Beim ersten Besuch von `/soziales`: Aktivierungsdialog mit klarem Hinweistext (welche Daten sichtbar werden, an wen). Ohne Aktivierung ist der Nutzer für andere unsichtbar und unauffindbar. Deaktivierung jederzeit in den Einstellungen (versteckt Profil, pausiert Eingang von Anfragen/Nachrichten). |
| **Datenminimierung (Art. 5)** | Nur Username-Referenzen und Nachrichtentexte – keine neuen Pflicht-Personendaten, keine Telefonnummern, keine Kontakte-Uploads. |
| **Privacy by Default (Art. 25)** | Sichtbarkeit ab Werk „nur Freunde"; Nachrichten nur zwischen bestätigten Freunden (verhindert nebenbei Spam). |
| **Löschung (Art. 17)** | Konto-Löschung kaskadiert per FK automatisch über Freundschaften, Blocks, Nachrichten und Challenges – nichts bleibt zurück. Einzelne eigene Nachrichten kann der Nutzer selbst löschen. |
| **Auskunft/Portabilität (Art. 15/20)** | Ausbau Phase 3: „Meine Daten exportieren"-Button (JSON-Dump der eigenen Profil-, Freundes-, Nachrichten- und Score-Daten). |
| **Vertraulichkeit (Art. 32)** | Transport via TLS (nginx). Speicherung serverseitig im Klartext in der eigenen Postgres – bewusste, dokumentierte Entscheidung (Moderationsfähigkeit); keine Weitergabe an Dritte, kein externer Dienst, keine Analytik. |
| **Transparenz (Art. 13)** | `datenschutz.html` um den Abschnitt FluxNet erweitern (Zwecke, Speicherdauer, Empfängerkreis „nur befreundete Nutzer", Moderationsregel); `nutzungsbedingungen.html` um Verhaltensregeln + Melde-/Sperrprozess. |
| **Betroffenenschutz** | Blockieren ist still (kein „X hat dich blockiert"); Melden einer Nachricht legt nur DIESE Nachricht dem Admin vor. |
### 3.6 Frontend
- `soziales.html` wird zum **FluxNet-Hub** (der Profil-Tab „Soziales" existiert schon): links Freundesliste (mit Online-Reihenfolge nach letzter Aktivität) + offene Anfragen + Username-Suche; rechts Chat-Panel des gewählten Freundes (Verlauf, Eingabefeld, „Nachricht melden/löschen" im Kontextmenü).
- Freundesprofil: Avatar, Level, Streak, Achievement-Vergleich (zwei Spalten, gemeinsame freigeschaltete hervorgehoben), Highscore-Tabelle je Spiel, Buttons „Herausfordern" / „Blockieren".
- Neue Dateien: `static/js/fluxnet.js` (IIFE, Polling-Logik, Render-Funktionen), `static/css/fluxnet.css` (Präfix `fn-`, nur Theme-Tokens, Erfolg blau ✓ / Fehler rot ✗). Ungelesen-Badge am Glocken-Icon der Dashboard-Topbar (zusammen mit dem FluxTimer-Popover aus Teil 4).
### 3.7 Phasen & Aufwand
1. **Phase 1 (≈ 7 PT):** Migrationen, Privacy-Opt-in, Freundschaften, Blockieren, Polling-Messenger, Ungelesen-Badge, Rechtstexte, Tests (Guard-Logik!).
2. **Phase 2 (≈ 4 PT):** SSE-Stream, Lern-Duelle, Freundesprofil mit Achievement-/Statistik-Vergleich, Melden + Admin-Moderationsansicht.
3. **Phase 3 (≈ 4 PT):** gemeinsame Lernräume (Seed-Boards), Datenexport (Art. 15/20), Feinschliff (Emoji-Reaktionen, Streak-Anstupsen).
---
## Teil 4: FluxTimer – Der Pausen-Lerntimer
> Opt-in-Erinnerungstimer im Look der Zeitschaltkreise des DeLorean: Nach frei wählbarer Zeit erinnert Mindflux ans Aufstehen, Trinken, Essen – oder was immer man einträgt. Standardmäßig **deaktiviert**, jederzeit abschaltbar, komplett frei einstellbar.
### 4.1 Funktionsumfang
- **Aktivieren/Deaktivieren** per Toggle (Einstellungen + Schnellzugriff über das Glocken-Icon der Dashboard-Topbar).
- **Intervall frei wählbar:** freie Minuteneingabe, sinnvolle Grenzen 5–240 Minuten (Default-Vorschlag 45).
- **Erinnerungsarten wählbar** (Mehrfachauswahl, Rotation): Aufstehen 🧍 · Trinken 💧 · Essen 🍎 · Augenpause 👀 · **eigener Text** (Freitextfeld).
- **Snooze** (+5 Minuten) und „Pause erledigt“ (startet das Intervall neu).
- **Probelauf-Button** in den Einstellungen (löst die Erinnerung sofort testweise aus).
- Optional zuschaltbar: Browser-Benachrichtigung (mit Permission-Abfrage) und dezenter Sound.
### 4.2 Technik: rein clientseitig, kein externer Dienst, DSGVO-minimal
- **`static/js/fluxtimer.js`** (IIFE), über das Layout-Fragment auf allen eingeloggten Seiten eingebunden.
- **Zustand in `localStorage`:** `fluxtimer = { enabled, intervalMinutes, reminders: ["aufstehen","trinken",…], customText, sound, notify, nextFireAt, lastFiredAt }`. Beim Seitenwechsel/Reload wird die Restzeit aus `nextFireAt` weitergeführt – der Timer „überlebt“ Navigation, ohne dass der Server irgendetwas weiß. **Kein Personenbezug, keine Serverdaten, nichts einwilligungspflichtig** (localStorage-Nutzung wird in der Datenschutzerklärung als technisch notwendig/funktional erwähnt).
- **Auslösung dreistufig:** (1) immer das In-App-Overlay (siehe 4.3); (2) optional lokale Browser-`Notification` – wird **lokal im Tab erzeugt**, kein Push-Server beteiligt; (3) optional kurzer Sound (Web Audio, eingebettet, kein CDN).
- **Bewusst kein Service-Worker-Web-Push:** Echte Push-Nachrichten bei geschlossenem Tab liefen zwingend über die Push-Gateways der Browserhersteller – das wäre ein externer Dienst und für einen Pausen-Timer unnötig (wenn kein Mindflux-Tab offen ist, lernt man gerade nicht in Mindflux).
- **Ausbau (optional, +0,5 PT):** `user_settings`-Tabelle für geräteübergreifende Einstellungen; localStorage bleibt der schnelle Cache.
### 4.3 BTTF-Optik („Zeitschaltkreise“)
- **Panel im Zeitschaltkreis-Look:** drei Anzeigezeilen in Segmentanzeigen-Typografie (Monospace + Glow):
- `NÄCHSTE PAUSE` – rot/bernsteinfarbene Anzeige (Countdown hh:mm:ss)
- `JETZT` – Akzentfarbe des Themes (laufende Uhrzeit)
- `LETZTE PAUSE` – gedimmt (wann zuletzt pausiert wurde)
- **Beim Auslösen:** kurzer Flux-Blitz (CSS-Animation über dem Overlay), 88-mph-Anspielung und Sprüche im BTTF-Ton, rotierend je Erinnerungsart – z. B. „Großer Scott! Zeit, etwas zu trinken.“ · „88 mph erreicht – kurz aufstehen, McFly!“ · „Dein Fluxkompensator braucht einen Snack.“
- **Styleguide-konform:** alle Farben über Theme-Tokens (die Rot-/Bernsteintöne als neue `--fluxtimer-*`-Tokens in `theme.css`, Dark/Light getestet); Grün bleibt tabu als Erfolgsfarbe. CSS in `static/css/fluxtimer.css` (Präfix `ft-`), Cache-Busting `?v=1`.
### 4.4 UI-Einstiegspunkte & Dateien
| Ort | Änderung |
|-----|----------|
| `settings.html` | Neue Sektion „FluxTimer“: Toggle, Minuten-Eingabe, Erinnerungs-Checkboxen, Freitext, Benachrichtigung/Sound-Optionen, Probelauf-Button |
| `dashboard.html` (Topbar) | Das bislang funktionslose Glocken-Icon öffnet ein FluxTimer-Popover: Status, Countdown, Schnell-Toggle, Link zu den Einstellungen |
| `templates/fragments/layout.html` | Script-Einbindung, damit der Timer auf allen eingeloggten Seiten weiterläuft |
| NEU `static/js/fluxtimer.js`, `static/css/fluxtimer.css` | Timer-Logik + Zeitschaltkreis-Optik |
### 4.5 Aufwand
≈ 1,5–2 PT (JS-Logik + Settings-UI + Popover + CSS/Animationen), +0,5 PT für die optionale serverseitige Persistenz.
---
## Schlüsseldateien als Bauplan-Referenz
| Datei | Rolle |
|-------|------|
| `src/main/java/com/example/mindflux/service/DevLabService.java` | Struktureller Bauplan für alle vier Services: `sanitize`/`check*`/`finish`/`awardedPoints`/`stars` + Text-Normalisierungs-Helfer (Checker für `choice`/`order`/`match`/`regex`/`cloze` teils 1:1 kopierbar) |
| `src/main/resources/static/js/devlabgame.js` | Renderer-Vertrag `{render, collect, lock, mark}`, Versuchsleiter-Frontend-Muster |
| `src/main/resources/db/migration/V8__devlab.sql` | SQL-Stilvorlage für V9–V12 |
| `src/main/java/com/example/mindflux/config/SecurityConfig.java` (Zeile 43) | CSRF-Ignore-Liste: `/api/codeescape/**`, `/api/bughunter/**`, `/api/serviceclash/**`, `/api/networker/**` ergänzen |
| `src/main/resources/templates/dashboard.html` (Zeilen 186–196) | `GAMES`-Array: Namen aufs neue Schema umstellen (DevLab, SyntaxSaga, CodeEscape, ServiceClash, BugHunter, NetWorker) und die vier `'/comingsoon'`-Hrefs gegen echte Routen tauschen (Icons `lock`/`sword`/`bug`/`network` existieren bereits) |
| `GameScoreController.KNOWN_GAMES` + `ProgressService.GAMES` | Vier reservierte Keys aufs neue Schema umbenennen (`codeescape`, `serviceclash`, `bughunter`, `networker`), `devlab` in `ProgressService` ergänzen (W1) |
| `src/main/java/com/example/mindflux/service/GameScoreService.java` | `recordScore(...)`-Anschlusspunkt aller Spiele |
| `STYLEGUIDE.md`, `CHANGELOG.md` | Design-Regeln (blau ✓ / rot ✗, nie grün; Theme-Tokens; `?v=N`) und Pflege der Historie |
## Verifikation
**Pro Spiel (nach Implementierung):**
1. `mvn test` – insbesondere `NetSimulatorTest` (Same-Subnet, Gateway, Longest-Prefix-Routen, Loop-Guard, Netz-/Broadcast-Adresse, Duplikat-IP) und `ServiceClashServiceTest` (Seed-Determinismus: gleicher Seed ⇒ identischer Plan; Re-Simulations-Scoring).
2. App starten (`docker-compose` oder lokal, Port 8282), einloggen und den vollen Spielfluss durchspielen: Lobby → Runde → absichtlich falsche Antworten (Versuchsleiter 1.0/0.6/0.3 prüfen) → Aufdecken (0 Punkte) → Abschluss → Score/Sterne/Highscore-Anzeige.
3. Server-Autorität prüfen: `POST /finish` mit manipulierten Daten (fremde IDs, unmögliche Zeitstempel) darf keinen überhöhten Score ergeben; View-DTOs im Netzwerk-Tab dürfen keine Lösungsfelder enthalten.
4. Highscore-Chip via `GET /api/games/<key>/stats`; Eintrag erscheint auf `/progress`.
5. Admin-CRUD: Anlegen/Ändern/Löschen inkl. Payload-Validierung (Netzwerker: Musterlösungs-Gate; Bug Hunter: `file`/`line`-Konsistenz; Service Clash: `correctIndex` in `options`).
6. Dark/Light-Theme umschalten und mobile Breite prüfen (Theme-Tokens, kein Grün).
**Namensschema & visuelle Differenzierung:** Dashboard zeigt alle neun Spiele mit einheitlichen CamelCase-Namen; jede der vier neuen Spielseiten hat ihren eigenen Akzent und ihr eigenes visuelles Vokabular – nebeneinander geöffnet müssen sie sofort unterscheidbar sein (Dark und Light prüfen).
**Navigation (Abschnitt 2.3):** Von jeder Profil-Unterseite aus sind alle anderen per Tab-Leiste erreichbar; „Profil" ist als Sidebar-Eintrag vorhanden; `settings.html` enthält keine funktionslosen Bedienelemente mehr; kein irreführender Chevron ohne Dropdown.
**Für die Verbesserungen (Teil 2):** Nach K1/K2: Neustart via docker-compose mit Secret-basiertem Passwort; `/users/save` liefert 404. Nach W1: DevLab erscheint auf `/progress`. Nach W4: bewusst kaputte Requests → saubere Fehlerseite/JSON-Fehler statt Whitelabel-500. Nach W6: doppelte Registrierung (Groß-/Kleinschreibung) liefert Fehlermeldung statt 500.