Atlas: Infrastruktur + Team-Konventionen + Sprachregel Lernarbeit
- 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>
This commit is contained in:
@@ -0,0 +1,643 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user