--- von: klima (Session vom 18.04. abends) an: klima (folgende Session) datum: 2026-04-18 22:40 status: uebergabe betrifft: Engine-Refactor — Schritt 1 + 2 fertig, Schritt 3-5 offen --- # Übergabe-Notiz: Klima-Engine-Refactor Auftrag aus `2026-04-18-2145-auftrag-engine-und-3d-v2.md`. Schritt 1 (Engine extrahieren) und Schritt 2 (2D auf Engine umstellen) sind durch. Schritt 3 (3D V2) wurde bewusst noch NICHT angefangen, weil der 4-6h-Block mehr Kontext braucht als in dieser Session verfügbar war. ## Was fertig ist ### Schritt 1 — `App/sims/klima/engine.js` Neues, headless JS-Modul, ESM-frei (`window.KlimaEngine`). Enthält: - **Konstanten:** `MEASURES`, `DIFFICULTY`, `ACHIEVEMENTS`, `CITIZEN_EVENTS`, `FIRST_BUY_HINTS`, `CO2_FLOOR`, `TEMP_FLOOR` — genau 1:1 aus der alten 2D-Version übernommen. - **Lebenszyklus:** `createGame(levelId, { maxTick, startYear, levelConfig })`, `serialize(game)`, `deserialize(data)`. - **Simulation:** `recalcEffects`, `computeClimate`, `computeTemperatureFromCO2`, `getEmissionsThisYear`, `getPowerDemand`, `getIncomeThisYear`, `getYearlyBalance`, `computePopulationGrowth`. - **Aktionen:** `buyMeasure(game, id, { resolveMeta, forceNegBalance })`, `demolishMeasure(game, id)`. `buyMeasure` liefert bei `reason: 'negative-balance'` eine Signal-Struktur — die View zeigt `confirm()`, und ruft `buyMeasure` erneut mit `forceNegBalance: true` auf. - **Tick:** `tick(game)` — simuliert ein Jahr, gibt `{ newEvents, unlockedAchievements, citizenEvent, endReason, timelineSample }` zurück. `endReason ∈ {null, 'won', 'pleite', 'ueberflutet'}`. - **Bürger-Events:** `maybeCitizenEvent`, `applyCitizenChoice`, `getCitizenEvent`. Choices sind `apply(game, ctx)`-Funktionen, `ctx = { resolveMeta, pushEvent }`. - **Achievements:** `tryUnlockAchievement(game, key)` — gibt Achievement-Objekt zurück, wenn frisch freigeschaltet, sonst null. `checkRenewable100(game)` wird intern in `buyMeasure` und `tick` aufgerufen. Wichtig: Engine-Events puffern sich in `game._pendingEvents` und `game._pendingAchievements`; `_beginEvents` / `_drainEvents` werden in jeder mutierenden Aktion aufgerufen, sodass der Return nur die DIESE-Aktion-Events enthält. ### Schritt 2 — `App/sims/klima/game-2d.html` Alle zuvor inline definierten Regel-Konstanten durch `const MEASURES = KlimaEngine.MEASURES;` etc. ersetzt. Alle Logik-Funktionen sind jetzt dünne Wrapper: - `state` ist jetzt `let state = KlimaEngine.createGame(1)` + `Object.assign(state, { phase, speed, MS_PER_TICK, tutorial*, pendingCitizenEvent })`. `startGame(lvl)` ersetzt `state` durch eine frische Engine-Instanz (dank `let` greifen alle View-Funktionen auf die neue Instanz zu). - `simulateTick()` ruft `KlimaEngine.tick(state)` auf und reicht das Resultat an `consumeEngineEvents`, `consumeEngineAchievements`, `triggerCitizenDialog`, `endLevel` weiter. - `buyMeasure(id)` / `demolishMeasure(id)` sind UI-Wrapper um die Engine; negative Bilanz wird weiterhin als confirm()-Dialog angezeigt. - `showEvent` wurde in `showEventUI(ev)` (reines UI, für Engine-Events) und `showEvent(icon,text,type,infoKey)` (Legacy-Einstieg, pusht + UI) aufgespalten, um doppelte Event-Stapel-Einträge zu vermeiden. - `unlockAchievement(key)` nutzt `KlimaEngine.tryUnlockAchievement`, `notifyAchievementUI(a)` macht Toast + Sound + Server-POST. - `serializeState` / `deserializeState` delegieren an die Engine und hängen View-Flags (phase, tutorial) an/lesen sie wieder aus. - `pickBuildPosition` bleibt View-seitig. `resolveMeta: () => ({ pos: pickBuildPosition(id) })` spreizt die Position in das Instance-Objekt, damit der bestehende Canvas-Draw-Code (`inst.pos.x/y`) unverändert läuft. Build-Positionen in `ownedMeasures[id].instances[i].pos.{x,y}` — das Format der Saves ist rückwärtskompatibel (alte Saves der V2-2D laden ohne Migration). ### Weitere Fixes / Hausaufgaben, die im selben Zug reingefallen sind - `.ggs-event-viewport` volle Canvas-Breite (10px Rand links/rechts), Toasts mit 8px/16px Innenabstand (flacher). - Hintergrundmusik: Default-Volume 22 %, Auto-Start bei erster User-Geste, wenn state.playing !== false. - Schornstein + Rauchfahne bei Häusern (in `drawHouse`): Rauch verblasst proportional zu `state.renewablePower / powerDemand`, volles 100 % erneuerbar → kein Rauch. - Helikopter in `drawAirport` fliegt nach seed-basiertem Zyklus (ca. 26-34 s): 8 % Start, 64 % Flug, 8 % Anflug, 20 % Pad-Parken. ### Gesendet: Start-Meldung `_inbox/zentrale/2026-04-18-2200-klima-engine-start.md` ging an Atlas raus ("Starte jetzt Schritt 1"). Eine Fertig-Meldung für Schritt 1+2 sollte raus, sobald die neue Session die 2D-Refactor-Regression-Tests durch hat (Level starten, Maßnahme kaufen, Bürger-Event beantworten, Save/Load, Level gewinnen/verlieren). ## Was noch offen ist ### Schritt 3 — `App/sims/klima/game-3d.html` (V2) **Der dickste Brocken.** Neue Datei, analoges Pattern wie 2D V2: - `` + `` - `` - Header mit Speed-Control, 📚-Button, 🔊 Mute, 🔄 Reset, 🎵 Music-Widget - Status-Panels links (Status, Bilanz), Action-Cards rechts, Graphen-Tabs, Event-Stapel wie in 2D - Glossar-Tooltips via `data-glossar` - Autosave localStorage-Key: `ggs-save-klima-3d-L{n}` - `ggsMusicSetup([...])` mit den 4 Klima-Tracks **Three.js per CDN:** Szenen-Baustellen aus `App/src/sims/sim-05-treibhaus-3d/*.ts` als Vorlage — NICHT importieren, sondern in Vanilla JS portieren. Übernehmen: - Scene + Camera + Lights + Insel-Geometrie - 3D-Modelle der Maßnahmen (Windrad, Solar, Deich, Mangrove …) - Drag-Orbit + Scroll-Zoom + Auto-Drohnen-Flug - Raycaster für Platzierung, Placement-Ghost (grün/rot) - Arrow-Keys für Rotation `resolveMeta` liefert in 3D etwas wie `{ pos: {x,y,z}, rotation, zoneId }` — Engine ist dafür agnostisch (spreizt nur in die Instance). ### Schritt 4 — `App/pages/klima-3d.php` Analog zu `klima-2d.php`, lädt `App/sims/klima/game-3d.html`. Level-Config kommt aus `game_levels` mit `game_id='klima'` (gleiche Quelle wie 2D — 2D und 3D teilen sich die Admin-Einstellungen). ### Schritt 5 — module_info / Lehrplan Nachricht an `_inbox/lehrplan/` schicken: - `module_info`-Eintrag `klima` um 2D/3D-Toggle erweitern - `modul-klima.php`-Detailseite: "▶ 2D spielen" / "▶ 3D spielen" Buttons ## Regression-Tests (für neue Session, bevor Fertig-Meldung) 1. `http://localhost/geograsim/App/pages/klima-2d.php?level=1` aufrufen 2. Level-Select → "Lernen" wählen → Spiel startet mit Budget 600, Pop 6000 3. Eine Windkraft kaufen → Toast, `🌬️ Windkraft-Pionier`-Achievement 4. Kohlekraftwerk kaufen → First-Buy-Hint "Kohle liefert …" 5. Einige Ticks laufen lassen → Timeline + Graphen füllen sich, Bürger-Event bei Tick 30 (Lina, Fischerin) → eine Option wählen 6. Reload → Spielstand wird wiederhergestellt 7. Reset → Speicher gelöscht 8. Level 2/3 durchlaufen, Events + Achievements vergleichen ## Bestätigen - Diese Notiz steht als `status: uebergabe` in klima-Inbox - Wenn neue Session anfängt: Schritt 3 starten, vorher in dieser Inbox checken, ob Atlas oder Thomas noch was drangelegt haben - Erwartete Kontext-Größe für Schritt 3 allein: vermutlich eine komplette Session (Three.js-Szenenaufbau + 12 Maßnahmen-Modelle + Platzierung)