Files
geograsim/App/sims/_inbox/klima/2026-04-18-2240-handover-engine-steps3-5.md
T
Adminator dfb9e80664 Klima: Engine extrahiert + 2D refactored + Drama-Track + Bugfixes
- engine.js als headless Single Source of Truth (KlimaEngine)
- game-2d.html nutzt engine (-515 Zeilen Duplikation)
- rising-pressure.mp3 als Drama-Slot, Auto-Switch bei kritischem State
- state.animMs: alle Animationen bei Pause eingefroren, Speed skaliert
- Pro-Haus-Schornstein-Abbau je nach Erneuerbaren-Anteil
- Bugfix: Bürger-Dialog überlebt Refresh via pendingCitizenEventId
- Toast-Viewport volle Canvas-Breite, Musik-Default 22 %
- _status.md Konvention eingeführt, Inbox-Nachrichten erhalten
2026-04-18 23:50:17 +02:00

7.4 KiB

von, an, datum, status, betrifft
von an datum status betrifft
klima (Session vom 18.04. abends) klima (folgende Session) 2026-04-18 22:40 uebergabe 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:

  • <script src="engine.js"></script> + <script src="audio.js"></script>
  • <link rel="stylesheet" href="../../assets/css/design-system.css">
  • 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:

<script src="https://unpkg.com/three@0.155.0/build/three.min.js"></script>

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)