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:
2026-04-19 11:51:14 +02:00
parent 2136bbbe62
commit 9a61f55cb1
134 changed files with 1012826 additions and 690 deletions
+643
View File
@@ -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.