dfb9e80664
- 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
161 lines
7.4 KiB
Markdown
161 lines
7.4 KiB
Markdown
---
|
|
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:
|
|
|
|
- `<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)
|