Files
mindflux/mindfluxfuture.md
T

64 KiB
Raw Blame History

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

  1. 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.
  2. CodeEscape (V9) – validiert die Wiederverwendung der DevLab-Checker/-Renderer mit minimalem Risiko. ≈ 5,5 PT
  3. BugHunter (V10) – übernimmt Terminal-Styling und Choice-Renderer aus Schritt 1. ≈ 4,5 PT
  4. NetWorker (V12) – Simulator zuerst, testgetrieben (pures Java, kein UI-Risiko), dann SVG-Frontend. ≈ 9,5 PT
  5. 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.

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.

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.