Files
geograsim/App/docs/module-interface.md
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

619 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# GeoGraSim — Module Interface Specification
Dieses Dokument definiert die Schnittstellen zwischen der Lernplattform und den einzelnen Simulationsmodulen. Jede Modul-Instanz (Claude Code Session) bekommt dieses Dokument als Kontext.
---
## 1. Architektur-Überblick
```
Lernplattform (PHP + MySQL) Simulationsmodul (HTML/JS/Canvas)
───────────────────────────── ─────────────────────────────────
Authentifizierung & Sessions → Spieler-Kontext (wer, welches Level)
Modul-Freigabe pro Klasse → Zugang ja/nein
Level-Konfiguration → Schwierigkeitsparameter
← Live-Fortschritt (während Spiel)
← Achievements (Badges, Sterne)
← Reflexionsantworten
← Abschluss-Assessment
Detail-View (Schüler) ← Modulspezifische Ergebnis-Daten
Detail-View (Lehrer) ← Aktionslog + Metriken
Summary-Kachel ← Standardisierte Zusammenfassung
```
---
## 2. Einbettung: PHP-Page pro Modul
Jedes Modul wird über eine PHP-Seite ausgeliefert: `App/pages/{modul-id}.php`
Die PHP-Seite:
1. Prüft die Session (Schüler oder Gast)
2. Lädt Level-Konfiguration aus der DB
3. Injiziert den Spieler-Kontext als JavaScript-Objekt
4. Lädt die HTML-Datei des Moduls (Template-Replacement oder direktes PHP)
### Spieler-Kontext (PHP → JS)
```html
<script>
window.__GGS__ = {
// Session
sessionId: '<?= $_SESSION["student_session"] ?? "" ?>',
studentId: <?= $_SESSION['student_id'] ?? 'null' ?>,
studentName: '<?= addslashes($_SESSION["student_name"] ?? "Gast") ?>',
classId: <?= $_SESSION['class_id'] ?? 'null' ?>,
// Modul
simId: 'klima', // eindeutige Modul-ID
simName: 'Klimawächter', // Anzeigename
level: 1, // aktuelles Level (1, 2, 3)
// Level-Konfiguration (aus DB: game_levels)
levelConfig: {
difficulty: 'easy',
timeLimit: 75, // Runden/Ticks
startBudget: 200,
eventFrequency: 0.3,
// ... modulspezifische Parameter
},
// Vorheriges Ergebnis (falls vorhanden)
previousScore: null, // oder {stars: 3, score: 72, ...}
// URLs
baseUrl: '/geograsim/App', // oder '' bei geograsim.at
apiUrl: '/geograsim/App/php/api'
};
</script>
```
---
## 3. API-Endpunkte (JS → PHP)
Alle API-Calls gehen an `{apiUrl}/{endpoint}.php` via `fetch()` mit JSON body.
### 3.1 Live-Fortschritt melden
```
POST /php/api/progress.php
{
"session_id": "uuid",
"sim_id": "klima",
"action": "update",
"data": {
"level": 1,
"phase": "playing", // "tutorial", "playing", "paused", "finished"
"tick": 34, // aktueller Spielfortschritt
"tick_max": 75, // maximale Ticks
"score_so_far": 45, // Zwischenstand (0100)
"metrics": { // modulspezifische Metriken
"co2": 780,
"temperature": 1.1,
"budget": 145
}
}
}
```
**Wann aufrufen:** Alle 10 Ticks oder bei wichtigen Ereignissen. Nicht bei jedem Tick — das wäre zu viel Traffic.
### 3.2 Achievement melden
```
POST /php/api/progress.php
{
"session_id": "uuid",
"sim_id": "klima",
"action": "achievement",
"data": {
"level": 1,
"badge_key": "first_windpark", // eindeutiger Key
"badge_name": "Windkraft-Pionier", // Anzeigename
"badge_icon": "🌬️", // Emoji
"badge_desc": "Ersten Windpark gebaut",
"xp": 50 // XP-Belohnung
}
}
```
### 3.3 Reflexionsantwort speichern
```
POST /php/api/progress.php
{
"session_id": "uuid",
"sim_id": "klima",
"action": "reflection",
"data": {
"level": 1,
"question": "Was war deine wirksamste Entscheidung?",
"answer": "Windpark gebaut",
"answer_index": 0, // Index der gewählten Option
"options": ["Windpark gebaut", "Küstenschutz ausgebaut", "Verkehr reduziert", "Ich bin mir nicht sicher"]
}
}
```
### 3.4 Abschluss-Assessment
```
POST /php/api/progress.php
{
"session_id": "uuid",
"sim_id": "klima",
"action": "submit_assessment",
"data": {
"level": 1,
"stars": 3, // 15
"score": 72, // 0100 (Gesamtbewertung)
"duration_ms": 324000, // Spielzeit in Millisekunden
"completed": true, // Level geschafft?
// Modulspezifische Ergebnisse
"results": {
"final_co2": 580,
"final_temperature": 1.3,
"final_budget": 42,
"final_flooded_pct": 12,
"renewable_pct": 78,
"measures_bought": 8,
"events_survived": 5
},
// Aktionslog (für Lehrer-Detailansicht)
"action_log": [
{"tick": 3, "action": "buy", "item": "windpark", "cost": 25},
{"tick": 5, "action": "buy", "item": "solar", "cost": 18},
{"tick": 8, "action": "buy", "item": "deich", "cost": 30},
{"tick": 12, "action": "event", "type": "sturmflut", "result": "survived"},
// ...
],
// Entscheidungseffizienz (automatisch berechenbar)
"efficiency": {
"score_per_action": 9.0, // score / anzahl_aktionen
"budget_efficiency": 0.71 // ergebnis / ausgegebenes_budget
},
// Badges verdient in diesem Level
"badges_earned": ["first_windpark", "coastal_defense"]
}
}
```
---
## 4. Summary-Format (für Klassenübersicht)
Jedes Modul liefert nach Abschluss eines Levels eine standardisierte Zusammenfassung. Die Plattform rendert daraus einheitliche Kacheln.
```json
{
"sim_id": "klima",
"sim_name": "Klimawächter",
"sim_icon": "🌍",
"level": 1,
"stars": 3,
"score": 72,
"completed": true,
"duration_formatted": "5:24",
"key_metric": {
"label": "Endtemperatur",
"value": "+1.3 °C",
"status": "warning"
},
"badges_count": 2
}
```
---
## 4a. Sprache — Lernarbeit statt Spielsprache (PFLICHT)
GeoGraSim ist kein Spiel, sondern eine **Simulations-Lernumgebung**.
Alle sichtbaren UI-Texte folgen dem Prinzip: **Lernarbeit**, nicht
Unterhaltung.
### Bildungstheoretischer Hintergrund
- **Wygotski**: Lernen als aktive Konstruktion in der Zone der nächsten
Entwicklung
- Sprache formt Haltung: "Spiel" → abschalten dürfen. "Arbeit" →
ernstgenommen werden.
- Gegenüber Eltern, Schulleitung, Lehrplan-Koordinator*innen:
"Simulation" ist verteidigungsfähig, "Spiel" nicht.
### Lexikon (verbindlich)
| NICHT verwenden | STATTDESSEN |
|-------------------------|---------------------------------------------|
| spielen, Spiel | arbeiten mit, Simulation, bearbeiten |
| Weiterspielen | Fortsetzen, weiterarbeiten |
| "Jetzt spielen" | "Simulation starten", "Los arbeiten" |
| Spielstand | Arbeitsstand, Fortschritt |
| Spieler*in | Lernende, Bearbeiter*in |
| Spielrunde | Durchgang, Arbeitsphase |
| Gameplay | Simulationsablauf, Arbeitsweise |
| "verloren", "Game Over" | "Ziel verfehlt", "Durchgang beendet" |
| Highscore | Beste Leistung, Bestwert |
| Level | OK (pädagogisch vertretbar), besser "Schwierigkeit" oder "Stufe" |
### Ausnahmen
- **Interne Variablen-/Tabellennamen** (`game.tick`, `gameState`,
`game_saves`, `game_levels`) sind weiterhin OK — nicht sichtbar,
keine pädagogische Wirkung.
- **Sichtbare UI-Texte**, **Marketing-Inhalte**, **Dokumentation für
Endnutzer** folgen strikt der Lexikon-Regel.
### Anwendung bei deinem Modul
- Prüfe bei jeder neuen UI-Zeile, bei jedem Overlay-Titel, bei jedem
Button-Text: Kommt "spielen" oder ein verwandtes Wort vor?
- Beim Übernehmen von V1-Texten: V1-Wortwahl korrigieren, nicht
1:1 kopieren.
- Tutorial-Karten, Achievements, Endscreen, Reflexionsfragen: besonders
sensibel, weil prominent sichtbar.
### Beispiele aus der Praxis
| Vorher (typisch V1) | Nachher (V2) |
|----------------------------------------|---------------------------------------|
| "Willkommen zum Klimaspiel!" | "Willkommen zur Klima-Simulation." |
| "Spiel starten" | "Simulation starten" |
| "Speichern & weiterspielen" | "Speichern & fortsetzen" |
| "Du hast gewonnen! 🏆" | "Ziel erreicht! 🏆" |
| "Game Over" | "Durchgang beendet" |
| "Dein Spielstand wird geladen ..." | "Dein Arbeitsstand wird geladen ..." |
| "Schwierigkeitsgrad wählen" | ok, oder "Stufe wählen" |
| "Dein Highscore" | "Deine beste Leistung" |
---
## 4b. Leichte Sprache (Barrierefreiheit, Inklusion)
Die Lehrperson kann pro Schüler*in in der Klassenliste den Flag
**"Leichte Sprache"** aktivieren. Wenn gesetzt, werden Texte im gesamten
System (Glossar-Tooltips, Beispiele, Modul-Beschreibungen, Info-Topics)
in vereinfachter Sprache ausgeliefert.
### Schüler-Flag in der DB
```sql
students.easy_language BOOLEAN NOT NULL DEFAULT 0
```
Setzen via:
```
POST /api/students {action: "update", studentId: N, easyLanguage: true}
```
### Was automatisch in Easy übersetzt wird (Backend-API)
| Tabelle | Normal-Spalte | Easy-Spalte |
|---|---|---|
| `glossar` | `short`, `text` | `short_easy`, `text_easy` |
| `glossar_examples` | `text` | `text_easy` |
| `module_info` | `short_desc`, `long_desc`, `learning_goals` | `short_desc_easy`, `long_desc_easy`, `learning_goals_easy` |
**Fallback-Logik:** Wenn die Easy-Spalte leer ist, liefert die API die
Normal-Version. So bricht nichts, wenn noch nicht alles übersetzt wurde.
### API-Verhalten
Die Glossar-API (`/api/glossar.php`) erkennt den aktiven Schüler automatisch
über die Session und liefert dann die Easy-Variante. Keine zusätzlichen
Parameter nötig.
Optional zum Testen / expliziten Überschreiben:
- `?easy=1` → erzwingt Easy
- `?easy=0` → erzwingt Normal
Response enthält ein Meta-Feld `_easy: true|false`, damit das Frontend
optional einen Hinweis "In leichter Sprache" einblenden kann.
### Wer schreibt die Easy-Versionen
- **Glossar-Instanz** schreibt `short_easy`, `text_easy`, `text_easy`
(Examples) für alle Glossar-Einträge
- **Lehrplan-Instanz** schreibt `*_easy` für `module_info`
- **Modul-Instanzen** schreiben eigene Event-Info-Topics direkt mit einer
Easy-Variante (wenn sie Info-Topics haben)
### Merkregeln für "Leichte Sprache"
- Kurze Sätze (max. ~1012 Wörter)
- Häufige, einfache Wörter
- Aktiv statt Passiv
- Fremdwörter vermeiden oder erklären
- Zahlen als Ziffern, nicht ausgeschrieben
- Keine Metaphern, keine Ironie
- Hauptaussage an den Anfang
### Richtlinien als Inspiration
- Netzwerk Leichte Sprache: https://www.leichte-sprache.org/
- capito Leichte Sprache Standards (A1 / A2 / B1)
---
## 4c. Referenzgerät — iPad Landscape (1180×820)
**iPad ist unser Hauptreferenzgerät.** Schulen in Österreich und DACH setzen
überwiegend iPads ein. Jedes Modul muss auf iPad Landscape wunderbar funktionieren.
### Layout-Breiten (aus design-system.css, automatisch)
| Viewport | Links | Canvas | Rechts |
|---|---|---|---|
| Desktop ≥1200px | 240px | 1fr | 280px |
| **iPad Landscape 9001199px** | **210px** | **1fr** | **250px** |
| iPad Portrait / Mobil <900px | einspaltig, Panels unter Canvas | | |
Die Breakpoints sind im Design-System gesetzt — Module müssen nichts dafür tun,
wenn sie `.ggs-sim-layout` verwenden.
### Pflichten für alle Module
**Touch-Ziele:** Alle Buttons, Cards und klickbaren Elemente mindestens
36×36px (idealerweise 40×40). Das ist in `.ggs-speed-btn`, `.ggs-btn-*`,
`.ggs-card` bereits so gesetzt. Eigene klickbare Elemente bitte auch so
dimensionieren.
**Kein Hover-Abhängigkeit:** Hover-Effekte sind schön, aber auf iPad gibt es
keinen Cursor. Jede Funktion, die per Hover erreichbar ist, muss auch per
Tap funktionieren. Hover ist Bonus, nicht Pflicht-UX. Im Design-System sind
Hover-Effekte schon in `@media (hover: hover)` gekapselt — Cards zeigen auf
Touch einen `:active`-Zustand statt klebendem Hover.
**Scrolling im Canvas:** Wenn das Canvas per Drag interagiert (3D-Orbit, Map-
Pan), muss verhindert werden, dass iPad Safari stattdessen die Page scrollt:
`touch-action: none` auf dem Canvas-Element setzen. Für Canvas-Zonen ohne
Drag-Interaktion nicht nötig.
**Kein Double-Tap-Zoom im Body:** Bereits im Design-System gesetzt
(`touch-action: manipulation` auf body). Kein Bedarf, das pro Modul zu
ändern — ausser du willst Pinch-to-zoom für ein spezielles Element
erlauben.
**Formular-Eingaben (Reflexion, Textfelder):** `font-size: 16px` mindestens,
sonst zoomt iPad Safari beim Fokus rein. Das Design-System setzt das für
Standardfelder bereits.
### Was noch zu testen ist
- Graph-Zoom per Tap: funktioniert
- Card-Tap: funktioniert, kein Hover-Kleber
- Speed-Buttons: gross genug
- Overlay-Schließen per Tap außerhalb: funktioniert
- Glossar-Tooltip per Tap öffnet, zweiter Tap außerhalb schließt: ✓
- Dropdown-Menüs (z.B. Country-Picker, falls im Header): Select statt
Custom-Dropdown, weil iPad native Selects perfekt rendert
---
## 5. Design-System
Alle Module binden `assets/css/design-system.css` ein. Diese Datei definiert:
### Farb-Variablen (Pflicht)
```css
--ggs-fjord /* Primärblau */
--ggs-moss /* Erfolg/Grün */
--ggs-sand /* Kosten/Neutral */
--ggs-coral /* Gefahr/Rot */
--ggs-orange /* Warnung */
```
### Layout-Zonen (verwende diese Klassen)
```
.ggs-header → App-Header (Logo + Textlogo + Separator + Modulname)
.ggs-sim-layout → 3-Spalten Grid (Standard: Canvas in der Mitte)
.ggs-sim-layout.fullscreen → Canvas randlos, Panels als Glass-Overlays darüber
.ggs-zone-status → Links: Parameter, Ziele, Badges
.ggs-zone-sim → Mitte: Canvas/3D/Karte
.ggs-zone-actions → Rechts: Maßnahmen/Werkzeuge/Missionen
.ggs-zone-graphs → Unten: Zeitverlaufs-Graphen (klickbar zum Vergrössern)
.ggs-event-viewport → In-Canvas Notifications (erscheinen gross, verschwinden)
.ggs-event-stack → Event-Stapel links unten (aufklappbar)
.ggs-loading → Ladebildschirm mit drehendem Logo
```
### UI-Komponenten (verwende diese Klassen)
```
.ggs-card → Auswahl-Card (Maßnahme, Werkzeug, Mission)
.ggs-param → Parameter-Anzeige mit Balken
.ggs-graph-card → Zeitverlaufs-Graph (SVG)
.ggs-event → Event-Pill im Feed
.ggs-speed → Geschwindigkeits-Buttons
.ggs-overlay → Modal/Overlay (Tutorial, Level-Ende, Endscreen)
.ggs-stars → Sterne-Bewertung
.ggs-toast → Achievement-Toast
.ggs-btn → Button (primary, secondary, ghost, success, danger)
.ggs-badge → Mini-Badge (cost, effect, protect, level-easy/medium/hard)
.ggs-phases → Phasen-Dots im Header
```
### Template-Datei
`App/sims/template.html` enthält alle Zonen mit Platzhalter-Inhalten und minimalen JS-Helfern:
- `showOverlay(id)` / `hideOverlay(id)`
- `showAchievement(icon, title, subtitle)` — Toast oben rechts
- `showEvent(icon, text, type)` — Notification im Canvas + Stapel (type: 'good'/'bad'/'neutral')
- `toggleEventStack()` — Event-Stapel auf/zuklappen
- `updateGraph(index, points, currentValue)` — Klick auf Graph vergrössert
- `updateParam(index, value, percent, trend)`
- `reportProgress(data)`
- `submitAssessment(results)`
- `submitReflection(level, question, answer)`
---
## 6. Modul-IDs
| ID | Name | Status |
|----|------|--------|
| `klima` | Klimawächter (2D + 3D) | Aktiv |
| `fluss` | Flussmanagement | Aktiv |
| `heli` | Heli-Navigation | Aktiv |
| `stadt` | Stadt & Raumplanung | In Entwicklung |
| `regenwald` | Regenwald-Expedition | Überarbeitung nötig |
---
## 7. Datenbank-Tabellen
### Gemeinsam (Plattform verwaltet)
- `teachers`, `classes`, `students` — Benutzerverwaltung
- `student_sessions` — Anonyme Sessions
- `game_levels` — Level-Konfiguration pro Modul × Schwierigkeit
- `player_progress` — XP, Level-Stand, Badges
- `assessment_answers` — Reflexionsantworten
- `class_modules` — Modul-Freigabe pro Klasse
- `licenses` — Lizenz-System
### Modulspezifisch (Modul verwaltet)
- `game_saves` — Spielstände (session_id + save_key)
- `assessments` — Detaillierte Ergebnisdaten (JSON-Felder)
- Module können eigene Tabellen anlegen (z.B. `geo_waypoints` für Heli)
---
## 7b. Persistenz-Pflicht: Autosave und Resume
**Kernprinzip:** Ein Browser-Reload (F5, Tab-Wechsel, Stromausfall, Schüler schliesst aus Versehen das Fenster) darf **niemals** zum Neustart des Spiels führen. Der Schüler muss dort weitermachen können, wo er aufgehört hat.
### Was das bedeutet
- Beim **Seitenaufruf / Reload** lädt das Modul den letzten gespeicherten Stand und setzt genau dort fort (gleiche Runde, gleiche Werte, gleiche platzierte Massnahmen, gleicher CO₂-Stand, etc.).
- Bei **Klick auf Reset** (und nur dann!) wird der Spielstand gelöscht und das Spiel beginnt von vorne.
- **Autosave** nach jeder wichtigen Aktion (Tick-Ende, Kauf einer Massnahme, Level-Übergang). Kein manuelles "Speichern"-Knopfdrücken nötig.
### Wie
Zwei Speicher, in dieser Reihenfolge:
1. **localStorage** (Primär): Sofortiges, synchrones Speichern im Browser. Funktioniert auch offline.
- Schlüssel-Format: `ggs-save-{modul-id}-{level}` (z.B. `ggs-save-klima-2`)
- Wert: JSON-Serialisierung des Spielzustands
2. **Server-Backup** (Sekundär, bei vorhandener Session): Zusätzlich per POST an `/api/saves.php` schicken, damit der Fortschritt auf anderen Geräten verfügbar ist. Fällt das Netz aus, ist localStorage der Fallback.
### Konkrete Implementierung
```javascript
// Speichern
function autoSave() {
const state = game.serialize(); // dein kompletter Spielzustand
const key = `ggs-save-klima-${game.level}`;
try { localStorage.setItem(key, JSON.stringify(state)); } catch {}
// Optional: Server-Spiegelung
if (window.__GGS__.sessionId) {
fetch(window.__GGS__.apiUrl + '/saves.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sim_id: 'klima',
save_key: `level-${game.level}`,
save_data: state
})
}).catch(() => {});
}
}
// Laden beim Start
function loadOrInit() {
const key = `ggs-save-klima-${level}`;
const raw = localStorage.getItem(key);
if (raw) {
try {
game.deserialize(JSON.parse(raw));
showEvent('💾', 'Spielstand geladen — weiter bei Runde ' + game.tick, 'neutral');
return;
} catch {}
}
// Fallback: neues Spiel
game.init();
}
// Reset-Knopf
function resetGame() {
if (!confirm('Fortschritt wirklich löschen und neu starten?')) return;
localStorage.removeItem(`ggs-save-klima-${game.level}`);
// Auch Server-Save löschen (DELETE), falls Session vorhanden
game.init();
}
```
### Wann speichern?
- Nach jedem Tick/Runden-Ende (automatisch)
- Nach jedem Kauf/Aktion (automatisch)
- Nach jeder Reflexionsantwort (automatisch)
- Bei Phasenwechsel (Tutorial → Spiel, Level → Level)
- Vor `beforeunload` als Sicherheit (falls Tab geschlossen wird)
### Welche Daten gehören in den Save
Alles, was der Spieler getan hat oder was sich durch Spielverlauf geändert hat:
- Aktuelle Runde / Tick
- Aktuelles Level
- Alle Spielparameter (Budget, CO₂, Temperatur, etc.)
- Gekaufte / platzierte Massnahmen inkl. Position
- Absolvierte Achievements / Badges
- Tutorial-Fortschritt (welche Schritte sind durch)
- Event-Historie (falls didaktisch relevant)
### UI-Hinweise
- Beim Laden eines bestehenden Stands: kurze Info einblenden ("Spielstand geladen — weiter bei Runde 34")
- Reset-Button (🔄) sichtbar im Header, mit Bestätigungsdialog ("Fortschritt löschen?")
- Save-/Load-Buttons im Header sind optional (für explizites Speichern) — Autosave passiert ohnehin
### Warum das wichtig ist
Didaktisch: Schüler*innen arbeiten in 45-Minuten-Einheiten. Wird das Modul aus Zeitgründen unterbrochen, soll in der nächsten Stunde dort weitergespielt werden können. Ohne Persistenz wäre das Modul unterrichtstauglich nur bei 100%-Fertigstellung in einer Einheit — unrealistisch.
Technisch: Browser-Crashs, versehentliche Reloads, Netzausfälle — alles passiert. Ohne Autosave verliert der Schüler seine Arbeit und frustriert sich. Ein Tabubruch im Lernkontext.
---
## 8. Datei-Struktur pro Modul
```
App/sims/{modul-id}/
├── game.html ← Hauptdatei (oder Template für PHP-Injection)
├── start.html ← Optional: Mini-Game (iframe)
├── landing.html ← Optional: Mini-Game (iframe)
└── assets/ ← Modul-eigene Assets (Sounds, Bilder)
App/pages/
├── {modul-id}.php ← PHP-Wrapper (Session, DB, Kontext-Injection)
└── {modul-id}-game.php ← Optional: Wenn Hauptseite DB-Daten braucht
```
---
## 9. Checkliste für neue Module
- [ ] Design-System CSS eingebunden
- [ ] Einheitlicher Header mit Logo, Modulname, Level-Badge
- [ ] Layout nutzt `ggs-sim-layout` Zonen
- [ ] Action-Cards nutzen `ggs-card` Klasse
- [ ] Parameter nutzen `ggs-param` mit Farbstufen
- [ ] Graphen nutzen `ggs-graph-card` (mindestens 1)
- [ ] Event-Feed zeigt Spielereignisse
- [ ] Speed-Control vorhanden (falls zeitbasiert)
- [ ] Tutorial-Overlay beim ersten Start
- [ ] Between-Level Screen mit Sterne + Reflexionsfrage
- [ ] Endscreen mit Stats-Grid
- [ ] Achievement-Toasts bei Meilensteinen
- [ ] API: `reportProgress()` alle 10 Ticks
- [ ] API: `showAchievement()` + POST bei Badge
- [ ] API: `submitAssessment()` bei Level-Ende
- [ ] API: `submitReflection()` bei Level-Übergang
- [ ] PHP-Page erstellt mit `window.__GGS__` Kontext
- [ ] **Autosave nach jeder Aktion** (localStorage + optional Server)
- [ ] **Resume beim Reload** (lädt letzten Stand, kein Neustart)
- [ ] **Reset-Button** löscht Save und startet neu (mit Bestätigungsdialog)