9a61f55cb1
- Design-System (assets/css/design-system.css) mit 21 Komponenten, iPad-Responsive-Breakpoints, Touch-Ziele 36px, Music-Player, Glossar-Tooltips - Templates (sims/template.html + student/teacher-Dashboard) - Docs: module-interface.md (inkl. 4a Sprachregel, 4b Leichte Sprache, 4c iPad), content-architecture.md, crash-recovery.md, music-registry.md - Admin-Infrastruktur: admin-modules.html + api/admin-modules.php (Titel, Emoji, Bild, Status, Dauer, Alter pro Modul) - Inbox-System: _inbox/README.md + _status.md fuer Atlas + Briefings an Klima, Glossar, Lehrplan, Fluss - Zentrale SFX-Pipeline (scripts/generate-sounds.py) - DALL-E-Bilder: 8 Badges + 5 Glossar-Repraesentationsbilder (Querformat) - Logo + Inter-Font lokal - PHP-APIs: admin, glossar, levels, licenses, progress, waypoints, assignments, profile, tickets - Spielsprache entfernt (admin-modules, admin-levels, schueler) - Landing-Page-Bearbeitungen (Boote sichtbarer, Button-Hintergrund) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
644 lines
20 KiB
Markdown
644 lines
20 KiB
Markdown
# GeoGraSim — Architektur & Schnittstellen
|
||
|
||
> **Zweck dieses Dokuments**
|
||
> Zentrale Referenz für alle Module, Datentypen, Schnittstellen und Konventionen.
|
||
> Geschrieben so, dass sowohl ein Mensch als auch eine KI das gesamte Projekt verstehen
|
||
> kann, ohne den Source-Code zu lesen. Bei jeder größeren Änderung dieses Dokument
|
||
> aktualisieren.
|
||
>
|
||
> **Stand:** April 2026 (nach Strom-System, 3 Schwierigkeitsgraden, Bilanz-UI, Steg-Rampe, Graph-Zoom, Auto-Pause)
|
||
|
||
---
|
||
|
||
## 1. Verzeichnis-Struktur
|
||
|
||
```
|
||
App/
|
||
├── index.html # Übersicht aller Simulationen
|
||
├── game-3d.html # Klimawächter 3D-Renderer (Hauptspiel)
|
||
├── game.html # Klimawächter 2D-Renderer (älter, hinkt hinterher)
|
||
├── erdbeben.html # SIM-07: Erdbeben
|
||
├── energiemix.html # SIM-09: Energiemix
|
||
├── stilauswahl.html # Frontend-Stil-Auswahl-Tool
|
||
│
|
||
├── src/
|
||
│ ├── core/
|
||
│ │ └── game-engine.ts # Basis-Klasse aller Simulationen
|
||
│ ├── ui/
|
||
│ │ ├── game-ui.ts # Generic GameUI (Resources, Goals, Shop, Graphs, ...)
|
||
│ │ ├── info-overlay.ts # Tooltips + erklärende Overlay-Modals
|
||
│ │ └── theme.ts # (falls vorhanden) Farb-Themes
|
||
│ └── sims/
|
||
│ ├── sim-05-treibhaus/ # Klimawächter Logik (engine-level)
|
||
│ │ ├── game.ts # KlimawaechterGame, MEASURES, DIFFICULTY_LEVELS
|
||
│ │ └── logic.ts # computeTemperature() etc.
|
||
│ ├── sim-05-treibhaus-3d/ # 3D-Renderer für Klimawächter
|
||
│ │ └── game-renderer-3d.ts (~1900 Zeilen)
|
||
│ ├── sim-07-erdbeben/ # SIM-07
|
||
│ └── sim-09-energiemix/ # SIM-09
|
||
│
|
||
├── tests/unit/
|
||
│ ├── sim-05-trace.test.ts # Mechanik-Trace über 75 Jahre, 7 Strategien
|
||
│ ├── sim-05-treibhaus.test.ts # Standard-Tests
|
||
│ └── ... (pro Sim)
|
||
│
|
||
└── docs/
|
||
├── architektur.md # ← DIESES DOKUMENT
|
||
├── klimawaechter-dev-log.html # Entwicklungs-Log
|
||
└── ...
|
||
```
|
||
|
||
---
|
||
|
||
## 2. Core-Schicht: `game-engine.ts`
|
||
|
||
Die `GameEngine`-Basisklasse stellt alle gemeinsamen Mechaniken bereit:
|
||
Ressourcen, Ziele, Tutorial, Events, Tick-Loop, Save/Load, Citizen-Events, State-Machine.
|
||
|
||
### 2.1 Lifecycle
|
||
|
||
```
|
||
constructor(meta) → setupResources() → setupGoals() → setupTutorial()
|
||
↓
|
||
state: 'tutorial' (warten auf Spieler-Klick durch Tutorial-Schritte)
|
||
↓
|
||
state: 'running' (msPerTick × speed → simulateTick() pro Zeitschritt)
|
||
↓
|
||
state: 'won' | 'lost' (checkWinCondition / checkLossCondition)
|
||
```
|
||
|
||
### 2.2 GameMeta
|
||
|
||
```typescript
|
||
interface GameMeta {
|
||
id: string // 'sim-05'
|
||
title: string // 'Klimawächter'
|
||
description: string
|
||
msPerTick: number // Wieviele ms pro Tick (4000 = 4 Sek/Jahr)
|
||
tickUnit: string // 'Jahr', 'Monat', etc. (UI-Beschriftung)
|
||
maxTicks: number // Spielende-Tick (75 für 2025-2100)
|
||
tutorialSteps: number // Anzahl der Schritte (siehe setTutorial)
|
||
}
|
||
```
|
||
|
||
### 2.3 Resources
|
||
|
||
```typescript
|
||
interface Resource {
|
||
id: string // eindeutig: 'budget', 'population', ...
|
||
name: string // Anzeige
|
||
icon: string // Emoji
|
||
initial: number
|
||
unit: string // 'Mio €', 'MW', 'cm', ...
|
||
format?: (v: number) => string // Custom Display-Format
|
||
current: number // wird vom Engine gehalten
|
||
}
|
||
|
||
// API:
|
||
addResource(r: Resource) // im constructor() aufrufen
|
||
setResource(id, value)
|
||
getResource(id): number
|
||
changeResource(id, delta)
|
||
getResourcesArray(): Resource[]
|
||
```
|
||
|
||
**Konvention:** Custom `format`-Funktionen können auf andere Ressourcen zugreifen:
|
||
```typescript
|
||
this.addResource({
|
||
id: 'power', initial: 0,
|
||
format: (v) => {
|
||
const demand = Math.round(this.getResource('population') / 1000)
|
||
return `${demand}/${Math.round(v)} MW`
|
||
}
|
||
})
|
||
```
|
||
|
||
### 2.4 Variables (interne, nicht angezeigte Werte)
|
||
|
||
```typescript
|
||
setVariable(key, value)
|
||
getVariable(key): number
|
||
```
|
||
|
||
Werden für interne Berechnungen verwendet, erscheinen nicht in der UI. Beispiele:
|
||
`co2Reduction`, `protection`, `upkeepTotal`.
|
||
|
||
### 2.5 Goals
|
||
|
||
```typescript
|
||
interface Goal {
|
||
id: string
|
||
title: string
|
||
description: string
|
||
check: (game: GameEngine) => boolean // erreicht?
|
||
progress?: (game: GameEngine) => number // 0-100 für Progress-Bar
|
||
required: boolean // muss für 'won' erfüllt sein
|
||
}
|
||
|
||
addGoal(g: Goal)
|
||
```
|
||
|
||
### 2.6 Events (Ereignis-Karten in der UI)
|
||
|
||
```typescript
|
||
addEvent(id, text, severity, infoKey?)
|
||
// severity: 'info' | 'success' | 'warning' | 'danger'
|
||
// infoKey: optional Schlüssel für INFO_TOPICS Lookup
|
||
getEvents(limit): Event[]
|
||
```
|
||
|
||
`id` macht es idempotent (jedes Event nur einmal). Mit `eventFired(id)` aus
|
||
einer Subklasse kann man prüfen, ob ein Event schon ausgelöst wurde.
|
||
|
||
### 2.7 Citizen Events (modale Bürger-Dilemmata)
|
||
|
||
```typescript
|
||
interface CitizenEvent {
|
||
id: string
|
||
character: string // Emoji-Avatar
|
||
title: string // 'Lina, die Fischerin'
|
||
message: string // 1-3 Sätze, kindgerecht
|
||
choices: CitizenChoice[]
|
||
}
|
||
|
||
interface CitizenChoice {
|
||
label: string // Button-Text
|
||
description: string // Vor-/Nachteile-Hint
|
||
apply: (game: GameEngine) => void
|
||
}
|
||
|
||
triggerCitizenEvent(ev: CitizenEvent)
|
||
getPendingCitizenEvent(): CitizenEvent | null
|
||
resolveCitizenEvent(choiceIdx: number)
|
||
```
|
||
|
||
Die UI zeigt das Modal automatisch an, wenn `pendingCitizenEvent !== null`.
|
||
**Nur eines gleichzeitig.**
|
||
|
||
### 2.8 Tick-Loop
|
||
|
||
```typescript
|
||
protected simulateTick(): void // ← in Subklasse implementieren
|
||
|
||
setSpeed(s: 0 | 1 | 2 | 4) // 0 = Pause
|
||
getSnapshot(): { tick, speed, state, goals[], timeline[] }
|
||
```
|
||
|
||
Die Engine ruft `simulateTick()` periodisch auf. `tick` zählt bei jedem Aufruf
|
||
hoch. Bei jedem Tick wird `notify()` gerufen → UI rendert neu.
|
||
|
||
### 2.9 Save / Load
|
||
|
||
```typescript
|
||
serialize(): string // JSON
|
||
deserialize(s: string): boolean // gibt false bei alter Version
|
||
|
||
// Subklassen-Hooks (überschreibbar):
|
||
protected serializeSubclass(): Record<string, unknown>
|
||
protected deserializeSubclass(data: Record<string, unknown>): void
|
||
```
|
||
|
||
**Versionierung:** `SAVE_VERSION = 2`. Saves mit anderer Version werden
|
||
abgelehnt → neues Spiel. Bei Mechanik-Änderungen Version erhöhen.
|
||
|
||
**Save-Format (Auszug):**
|
||
```json
|
||
{
|
||
"version": 2,
|
||
"tick": 12,
|
||
"state": "running",
|
||
"speed": 1,
|
||
"resources": { "budget": 437, ... },
|
||
"variables": { "co2Reduction": 1.2, ... },
|
||
"goalsAchieved": ["temp"],
|
||
"events": [...],
|
||
"subclass": { /* serializeSubclass() output */ }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. UI-Schicht: `game-ui.ts`
|
||
|
||
Generische UI-Klasse, die von jeder Sim-HTML-Seite verwendet wird. Erwartet
|
||
DOM-Elemente mit bestimmten IDs (siehe unten) und rendert da hinein.
|
||
|
||
### 3.1 GameUIConfig
|
||
|
||
```typescript
|
||
interface GameUIConfig {
|
||
game: GameEngine
|
||
Renderer: new (container: HTMLElement, game: any) => Renderer
|
||
shopItems: ShopItem[]
|
||
onBuy: (id: string) => boolean
|
||
onDemolish?: (id: string) => boolean // Optional: 🗑-Button
|
||
getOwnedCount?: (id: string) => number // Optional: ×N Badge
|
||
graphs: GraphConfig[]
|
||
saveKey: string // localStorage key
|
||
finishOnTick?: number
|
||
yearOffset?: number // 2025
|
||
mode?: 'free' | 'guided'
|
||
totalDurationSec?: number // bei mode=guided
|
||
statusResources?: string[] // Whitelist welche Resources im Status-Panel
|
||
}
|
||
```
|
||
|
||
### 3.2 ShopItem
|
||
|
||
```typescript
|
||
interface ShopItem {
|
||
id: string
|
||
name: string
|
||
emoji: string
|
||
description: string
|
||
cost: number
|
||
upkeep: number
|
||
badges?: { label: string; type: BadgeType }[]
|
||
}
|
||
|
||
type BadgeType = 'cost' | 'reduction' | 'protection' | 'capacity' | 'quality' | 'neutral'
|
||
```
|
||
|
||
### 3.3 GraphConfig
|
||
|
||
```typescript
|
||
interface GraphConfig {
|
||
id: string // SVG-Element-ID
|
||
title: string
|
||
field: string // Resource-ID, dessen Werte geplottet werden
|
||
yMin: number
|
||
yMax: number
|
||
color: string // CSS-Farbe
|
||
unit?: string
|
||
zones?: GraphZone[] // farbige Bänder im Hintergrund
|
||
lines?: GraphLine[] // horizontale Referenzlinien
|
||
autoScale?: boolean // yMax wandert mit Daten mit
|
||
}
|
||
```
|
||
|
||
### 3.4 Erwartete DOM-Element-IDs (HTML-Seite)
|
||
|
||
| ID | Inhalt |
|
||
|---|---|
|
||
| `#canvas-wrap` | Container für 3D/2D Renderer |
|
||
| `#resources` | Status-Box (Resources werden hier eingefügt) |
|
||
| `#status-goals` ODER `#goals` | Ziele-Liste |
|
||
| `#measures` | Shop |
|
||
| `#events` | Vollständige Ereignis-Liste (Popout) |
|
||
| `#events-latest` | Nur das letzte Event (Trigger in Status-Box) |
|
||
| `#tutorial`, `#tut-title`, `#tut-text`, `#tut-dots`, `#tut-next` | Tutorial-Modal |
|
||
| `#end-screen-host`, `#citizen-event-host`, `#info-overlay-host` | Modal-Slots |
|
||
| `[data-speed="0|1|2|4"]` | Speed-Buttons |
|
||
| `#btn-save`, `#btn-load`, `#btn-finish`, `#btn-reset` | System-Buttons |
|
||
|
||
### 3.5 Sim-Spezifische UI-Sonderfälle (Duck-Typing)
|
||
|
||
`GameUI` ist generisch, hat aber **drei Hooks** für sim-spezifische UI-Erweiterungen:
|
||
|
||
1. **`power`-Resource** → `updatePowerGrid()` rendert Bedarf/Kapazitäts-Balken
|
||
2. **`budget`-Resource + `game.getYearlyBalance()`** → `updateBudgetBalance()` rendert Bilanz-Tabelle
|
||
3. **Resource-Format-Closures** dürfen `this` (das Game-Objekt) nutzen
|
||
|
||
**Konvention:** Sim-spezifische UI-Logik in `game-ui.ts` mit Kommentar
|
||
`// Spezialfall <NAME>:` markieren.
|
||
|
||
### 3.6 Public API (von außen aufrufbar)
|
||
|
||
```typescript
|
||
ui.getRenderer(): Renderer | null // für Kamera-Steuerung
|
||
ui.redrawGraphs(): void // erzwingt Graph-Neuzeichnung (nach Resize)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. Renderer-Schicht: `game-renderer-3d.ts`
|
||
|
||
Three.js-basierter 3D-Renderer. Konstruktor erhält `(container, game)` und
|
||
hängt sich selbst an `container`.
|
||
|
||
### 4.1 Erwartete Methoden eines Renderers
|
||
|
||
```typescript
|
||
interface Renderer {
|
||
start(): void
|
||
stop(): void
|
||
// optional, für UI-Buttons:
|
||
zoomIn?(): void
|
||
zoomOut?(): void
|
||
recenter?(): void
|
||
}
|
||
```
|
||
|
||
### 4.2 KlimawaechterRenderer3D — interne Struktur
|
||
|
||
```
|
||
Scene
|
||
├── Wasserfläche (steigt mit sealevel)
|
||
├── Himmel-Kuppel
|
||
├── Insel (PlaneGeometry mit Vertex-Colors, 120×160 Segmente)
|
||
├── Vulkan-Kuppel + Schnee-Gletscher (LatheGeometry, ClippingPlane)
|
||
├── Steg + Rampe + wartende Bewohner-Sprites
|
||
├── villageGroup (Häuser + 3 Baumtypen mit Stadien)
|
||
├── smokeGroup (Heizwolken pro Haus)
|
||
├── fxGroup (FX-Effekte: Totenkopf, Boot, Skulls)
|
||
├── placed[] (gekaufte Maßnahmen)
|
||
└── mountainShieldGroup? (wenn hasMountainShield)
|
||
```
|
||
|
||
### 4.3 Wichtige Konstanten
|
||
|
||
```typescript
|
||
ISLAND_HALF_WIDTH = 13 // X-Halbachse
|
||
ISLAND_HALF_LENGTH = 17 // Z-Halbachse
|
||
WATER_Y_BASE = 0.0 // Start-Wasserlinie
|
||
MAX_SEA_RISE_UNITS = 1.6 // Wasser steigt bis +1.6 Y bei 200 cm Sealevel
|
||
VOLCANO_X = -3.0
|
||
VOLCANO_Z = -11.0
|
||
VOLCANO_DOME_HEIGHT = 2.1
|
||
VOLCANO_DOME_RADIUS = 2.2
|
||
ZONE_BEACH_END = 0.38 // Vertex-Color-Zonen
|
||
ZONE_MEADOW_END = 0.95
|
||
ZONE_FOREST_END = 1.80
|
||
```
|
||
|
||
### 4.4 FX-System
|
||
|
||
```typescript
|
||
interface FxEffect {
|
||
root: THREE.Object3D
|
||
lifetime: number
|
||
age: number
|
||
update: (dt: number, ef: FxEffect) => boolean // false → entfernen
|
||
}
|
||
|
||
private fxEffects: FxEffect[] = []
|
||
private fxGroup: THREE.Group
|
||
|
||
// Pro Frame in updateScene():
|
||
for (let i = fxEffects.length - 1; i >= 0; i--) {
|
||
if (!fxEffects[i].update(dt, fxEffects[i])) fxEffects.splice(i, 1)
|
||
}
|
||
```
|
||
|
||
Spawn-Helpers:
|
||
- `spawnSkull(x, y, z)` — 💀 schwebt 1.2 Units nach oben, 3.2s Lebensdauer
|
||
- `spawnRefugeeBoat()` — Boot mit Boarding → Spirale → Escape (28s total)
|
||
- `makeEmojiSprite(emoji, size)` — Billboard-Sprite mit CanvasTexture
|
||
|
||
### 4.5 Maßnahmen-Platzierung
|
||
|
||
```typescript
|
||
private placeMeasure(typeId: string, index: number): THREE.Object3D | null
|
||
```
|
||
|
||
Switch über `typeId`. Pro neuem Measure-Typ ein neuer Case + ein
|
||
`makeXxx()`-Helper. Das Renderer-Update-Loop scannt `game.getOwnedMeasures()`
|
||
und ruft `placeMeasure` für neue, entfernt Meshes für demolierte.
|
||
|
||
---
|
||
|
||
## 5. Sim-Spezifisch: `sim-05-treibhaus/game.ts`
|
||
|
||
### 5.1 KlimawaechterGame
|
||
|
||
Erbt von `GameEngine`. Ungefährer Aufbau:
|
||
|
||
```typescript
|
||
export class KlimawaechterGame extends GameEngine {
|
||
private measures: OwnedMeasure[]
|
||
private diff: DifficultyConfig
|
||
private co2Ppm = 425
|
||
private currentTemp = 0
|
||
private targetTemp = 0
|
||
private seaLevelCm = 0
|
||
private floodedHouses = 0
|
||
researchDiscount = 1.0
|
||
tourismMode = false
|
||
hasMountainShield = false
|
||
treesChoppedForHeat = 0
|
||
private blackoutStreak = 0
|
||
private firedEvents = new Set<string>()
|
||
|
||
constructor(difficulty: 1|2|3 = 2)
|
||
|
||
// Public API
|
||
buyMeasure(id): boolean
|
||
demolishMeasure(id): boolean
|
||
getOwnedMeasures(): OwnedMeasure[]
|
||
getMeasureCount(id): number
|
||
getDifficulty(): DifficultyConfig
|
||
getYearlyBalance(): { income, tourism, upkeep, climate, net }
|
||
getIncomeAtCurrentLevel(): number
|
||
|
||
// Subclass-Hooks
|
||
protected simulateTick()
|
||
protected serializeSubclass()
|
||
protected deserializeSubclass()
|
||
protected checkLossCondition()
|
||
}
|
||
```
|
||
|
||
### 5.2 MEASURES-Tabelle (Stand April 2026)
|
||
|
||
| id | name | cost | upkeep | co2Reduction | protection | powerOutput |
|
||
|---|---|---|---|---|---|---|
|
||
| `forest` | 🌲 Wald | 12 | 0.5 | -0.04 | – | – |
|
||
| `solar` | ☀️ Solar | 40 | 2 | -0.14 | – | +1 MW |
|
||
| `wind` | 🌬 Wind | 150 | 4 | -0.50 | – | +3 MW |
|
||
| `green-roof` | 🏡 Gründach | 30 | **0** | -0.04 | – | – |
|
||
| `dike` | 🌊 Deich | 80 | 3 | – | +12 cm | – |
|
||
| `sea-wall` | 🛡 Hochw.schutz | 240 | 6 | – | +50 cm | – |
|
||
| `coal` | 🏭 Kohle | 20 | 1 | **+0.38** | – | +4 MW |
|
||
| `airport` | ✈️ Flughafen | 62 | 3 | **+0.22** | – | – |
|
||
| `cloud-seed` | 🌤 Wolken-Impfung | 125 | 4.5 | -0.04 | – | – |
|
||
|
||
**Differenzierung:**
|
||
- **Solar vs Wind:** Solar günstig pro Stück, Wind effizienter pro MW Betrieb
|
||
- **Wald:** Günstigste Wartung pro CO₂-ppm (didaktisch: Naturlösung)
|
||
- **Gründach:** Einzige Maßnahme mit 0 Wartung (Set-and-forget)
|
||
- **Deich vs Hochwasserschutz:** Niedrige Hürde vs effiziente Großlösung
|
||
- **Kohle/Flughafen/Wolken:** Negative oder unwirksame Maßnahmen (Lehrtraps)
|
||
|
||
### 5.3 DIFFICULTY_LEVELS
|
||
|
||
```typescript
|
||
DIFFICULTY_LEVELS[1] // 🟢 Lernen: 750/220, 3J Schonfrist, ×0.5 Folgen
|
||
DIFFICULTY_LEVELS[2] // 🟡 Üben: 550/175, 1J Schonfrist, ×1.0 Folgen
|
||
DIFFICULTY_LEVELS[3] // 🔴 Profi: 400/145, 0J Schonfrist, ×1.5 Folgen
|
||
```
|
||
|
||
Beeinflusst: `startBudget`, `incomePer10k`, `blackoutGrace`, `treesPerBlackout`,
|
||
`co2BlackoutPerYear`, `climateDamageMul`, `popLossMul`.
|
||
|
||
### 5.4 Klimaberechnung (`computeClimate()`)
|
||
|
||
```
|
||
targetTemp = computeTemperature(co2Ppm, sensitivity=0.3)
|
||
currentTemp += (targetTemp - currentTemp) * 0.08 // Trägheit (Ozean)
|
||
sealevelTarget = (currentTemp - 15) * 30 cm/°C
|
||
sealevelCm += (target - current) * (rising ? 0.08 : 0.005) // committed rise
|
||
floodedHouses = max(0, (sealevel - protection - 35) * 0.9) [%]
|
||
```
|
||
|
||
### 5.5 simulateTick — Reihenfolge
|
||
|
||
```
|
||
1. Steuern (income = popRatio × diff.incomePer10k)
|
||
1b. Tourismus (Bonus + CO₂, oder Kollaps wenn Strand weg)
|
||
2. Wartung
|
||
3. CO₂-Update (emissions - reduction)
|
||
4. computeClimate()
|
||
4b. Strom-Bilanz + Stromausfall-Folgen (Bäume + CO₂ + Pop-Drift)
|
||
5. Häuser-Überflutung
|
||
5b. Sealevel-Folgen (Versalzung, Pop-Drift)
|
||
6. Zeit-Events
|
||
7. Citizen-Events (maybeTriggerCitizenEvent)
|
||
```
|
||
|
||
### 5.6 Citizen Events
|
||
|
||
| Tick | ID | Trigger |
|
||
|---|---|---|
|
||
| 15 | `citizen-fisher` | Lina, Fischerin (warmes Wasser) |
|
||
| 9 | `citizen-scientist` | Dr. Hassan, Forschungszentrum |
|
||
| 14 | `citizen-farmer` | Yusuf, Bewässerung |
|
||
| 20 | `citizen-tourism` | Maria, Hotel |
|
||
| 28 | `citizen-youth` | Lia, 14, Streik |
|
||
| 38 | `citizen-industry` | Konzernchef Vogel |
|
||
| 50 | `citizen-mountain` | Anna, Bergdorf |
|
||
|
||
---
|
||
|
||
## 6. Auto-Pause-System (in HTML-Seite)
|
||
|
||
In `game-3d.html` als Token-basiertes Pause-System:
|
||
|
||
```javascript
|
||
const overlayPauseTokens = new Set()
|
||
let speedBeforeOverlayPause = 1
|
||
|
||
function pauseGameForOverlay(token) { ... }
|
||
function resumeGameForOverlay(token) { ... }
|
||
|
||
// Auto-Hooks via MutationObserver:
|
||
observeOverlayForPause('.citizen-overlay', 'citizen-event')
|
||
observeOverlayForPause('.info-overlay', 'info-overlay')
|
||
|
||
// Manuell für Graph-Zoom + Events-Popout
|
||
```
|
||
|
||
**Konvention:** Jeder neue Overlay-Typ registriert sich mit eigenem Token.
|
||
Beim letzten Schließen wird `speedBeforeOverlayPause` wiederhergestellt.
|
||
|
||
---
|
||
|
||
## 7. Konventionen & Best Practices
|
||
|
||
### 7.1 Tooltip-System
|
||
|
||
**Globaler Listener:** `installTooltips()` in `info-overlay.ts` registriert
|
||
einen delegated `mouseover`-Handler. Jedes Element mit `data-tip="key"`
|
||
zeigt automatisch das `TIPS[key]`-Tooltip beim Hover.
|
||
|
||
```html
|
||
<span data-tip="ppm">ppm <span class="unit-i">i</span></span>
|
||
```
|
||
|
||
**Erweitern:** Neuen Eintrag in `TIPS: Record<string, TipDef>` hinzufügen.
|
||
|
||
### 7.2 Display-Animation
|
||
|
||
Numeric Resources werden via gedämpfter rAF-Loop (12% pro Frame) zum
|
||
Zielwert hingezogen. Implementiert in `GameUI.ensureDisplayAnim()`.
|
||
Snapping bei `absDelta < 0.5`.
|
||
|
||
### 7.3 Graph-Zoom (FLIP-Pattern)
|
||
|
||
Beim Klick auf eine `.graph-card`:
|
||
1. Card via `appendChild(body)` aus Container herauslösen (wegen `backdrop-filter`)
|
||
2. Platzhalter im Original-Grid lassen
|
||
3. `position: fixed` mit Original-Rect setzen, Reflow erzwingen
|
||
4. Transition zu zentrierten Größen
|
||
5. Nach Animation `ui.redrawGraphs()` für SVG-Resize
|
||
|
||
Bei Unzoom: zurück animieren, Card in Platzhalter-Position wieder einfügen,
|
||
Inline-Styles aufräumen.
|
||
|
||
### 7.4 Save-Versionierung
|
||
|
||
Bei jeder Mechanik-Änderung in einer Sim:
|
||
1. `SAVE_VERSION` in `game-engine.ts` erhöhen (oder Sim-spezifisches `SCHEMA_VERSION` falls vorhanden)
|
||
2. Alte Saves werden automatisch verworfen
|
||
3. UI zeigt freundlichen Hinweis
|
||
|
||
### 7.5 Trace-Tests
|
||
|
||
`tests/unit/sim-05-trace.test.ts` simuliert verschiedene Strategien rein
|
||
rechnerisch über alle 75 Jahre und prüft, dass:
|
||
- Optimale Strategie gewinnt
|
||
- "Nichts tun" verliert
|
||
- "Nur Wald" verliert (zu langsam)
|
||
- "Nur Deich" verliert (CO₂ killt sie trotzdem) — **TODO**: aktuell unsicher
|
||
|
||
---
|
||
|
||
## 8. Ranking-System (vorgeschlagen, noch nicht implementiert)
|
||
|
||
Erweitere `GameMeta` um:
|
||
|
||
```typescript
|
||
interface GameRanking {
|
||
complexity: 1|2|3|4|5
|
||
timeMinutes: number
|
||
gradeRecommendation: { min: number; max: number } // 1-12 Schulstufe
|
||
focusedLearning: 1|2|3|4|5
|
||
topics: string[]
|
||
}
|
||
```
|
||
|
||
### Klimawächter (sim-05) Vorschlag:
|
||
|
||
```typescript
|
||
ranking: {
|
||
complexity: 4,
|
||
timeMinutes: 15,
|
||
gradeRecommendation: { min: 6, max: 8 },
|
||
focusedLearning: 3,
|
||
topics: ['Klimawandel', 'Energiewende', 'Anpassung vs Mitigation', 'Wirtschaftsbilanz']
|
||
}
|
||
```
|
||
|
||
`index.html` filtert/sortiert dann Sims nach Ranking.
|
||
|
||
---
|
||
|
||
## 9. Offene Punkte (kurzgefasst)
|
||
|
||
| Was | Wo dokumentiert |
|
||
|---|---|
|
||
| Phase B: Drag-and-Drop-Baueditor | dev-log §4 |
|
||
| Phase C: Wohnungsbau + Neuankömmlinge | dev-log §4 |
|
||
| Mitigation vs Adaptation Kipppunkt | dev-log §4 (idee) |
|
||
| Insel-Leben Sprites (Wolken, Möwen, Boote) | brainstorm offen |
|
||
| Sand-Aufschüttung mit Erosion | brainstorm offen |
|
||
| Mangroven (Win-Win-Maßnahme) | brainstorm offen |
|
||
| Sterne-Ranking implementieren | dieses Dok §8 |
|
||
| 2D-Renderer auf Strom-System updaten | dev-log §8 |
|
||
|
||
---
|
||
|
||
## 10. Konventionen für die KI
|
||
|
||
Wenn die KI Code-Änderungen macht:
|
||
|
||
1. **Erst lesen, dann ändern.** Nie Annahmen über Code, der nicht im Kontext ist.
|
||
2. **Build + Tests nach jeder Änderung.** `npx vite build && npx vitest run`.
|
||
3. **Headless-Verify für UI-Änderungen.** Playwright-Script in `App/scripts/`.
|
||
4. **Save-Schema bei Mechanik-Änderung erhöhen.** Sonst kaputte Saves.
|
||
5. **Trace-Test grün halten.** Wenn er rot wird: Balance kalibrieren, nicht ignorieren.
|
||
6. **Diese Doku aktualisieren** bei Schnittstellen-Änderungen.
|
||
7. **Wald = günstigste CO₂-Reduktion pro Mio €.** Heilig. Nie verschlechtern.
|
||
8. **Mio €** ist die Geldeinheit. Nie nackte Euro.
|
||
9. **Tick = Jahr** in sim-05. Falls Wechsel auf Monate: alle Werte ÷12.
|
||
10. **Einfache emojis bevorzugen** (Single-Codepoint, kein ZWJ). 🧍 ja, 🧍♀️ nein.
|