---
instanz: logistik
rolle: Simulation „Logistik Europa" (Modul 11)
stand_seit: 2026-04-19
phase: Phase 8d — Integrations-Vertrag §6 umgesetzt (mode-check + assessment-calls) + L1-UI-Polish + Manueller Fahrzeugkauf ab L2. Wartet auf Atlas-Reviews (3 offene Mails)
---

# Status — Logistik-Instanz

## Rolle und Scope
- Eigner: `App/sims/logistik/` (Gerüst kommt von Atlas)
- Eigner: `App/pages/logistik.php`, `App/pages/modul-logistik.php`
- Eigner: `App/php/api/logistik-*.php`
- Optional: neue MySQL-Tabellen `lg_…` (nur nach Atlas-Abstimmung)
- Plattform-Zentrale ist Atlas — Anfragen über `_inbox/zentrale/`

## Aktueller Stand
- 2026-04-19 23:15 — Kickoff-Briefing von Atlas erhalten
- 2026-04-19 23:25 — Briefing + Pflichtenheft (alle Schlüsselkapitel) gelesen
- 2026-04-19 23:30 — Empfangsbestätigung an Zentrale geschickt
- 2026-04-20 00:10 — Atlas-Antworten auf alle 4 Rückfragen erhalten
- 2026-04-20 00:20 — Quittung an Atlas (Routing bestätigt, alle Punkte übernommen)
- Warte auf: Atlas baut Gerüst (module_info, Landing-Card, Detailseite, Wrapper, Skelette unter `App/sims/logistik/`)

## Geklärte Entscheidungen (Atlas-Antwort 00:10)
- **Lehrplan**: `kompetenzen.json` als Vorab-Skizze (`status: "proposed"`) nach Gerüst, final nach Phase 2
- **Glossar**: Atlas koordiniert Anfrage; ~10 von 13 Begriffen neu, reicht bis Phase 6
- **Landing**: Eigene Gruppe „Wirtschaft & Verkehr" (LOCKED bis FREE)
- **Tile-Server**: Carto Positron `https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png`, Leaflet 1.9.4, `minZoom: 4, maxZoom: 10`
- **Routing**: bestätigt — L1 Luftlinie×1.3, L2/L3 hand-gepflegte Polylines in `lg-routes-osm.json`, OSRM erst auf Thomas-Wunsch
- **State-Machine**: Atlas legt Enum exakt nach Pflichtenheft Kap 44.2 ins `engine.js`-Skelett

## Phasenplan (aus Briefing)
- [ ] **Phase 0** — Balance-Matrix + Test-Harness + Headless-Runner-Skelett → Atlas-Review
- [ ] Phase 1 — Fundament (engine.js + Karte + Seed-Daten)
- [ ] Phase 2 — Kern-Loop (Level 1 spielbar)
- [ ] Phase 3 — Kostenmodell + Mehrfahrzeuge + mehrere Aufträge (Level 2)
- [ ] Phase 4 — Bahn + Häfen + Intermodal (Level 3)
- [ ] Phase 5 — Hilfestufen + Events
- [ ] Phase 6 — Minigames + Sprachregel-Check + Leichte Sprache
- [ ] Phase 7 — Lehrkraftmodus + Analytics + Polish

## Offene Aufgaben (kurzfristig)
1. `App/docs/module-interface.md` Abschnitte 4a/4b/4c/7b lesen
2. Sobald Gerüst von Atlas steht: `balance-matrix.md` schreiben (Werte aus Kap 65 + Schätzwerte)
3. `test.html` mit 7 Pflichttests anlegen
4. Headless-Runner-Skelett `runLevel(levelId, seed, strategy)` definieren
5. Atlas-Review von Phase 0 anfordern, bevor Phase 1 startet

## Blocker
- Keine. Wartepunkt: Atlas-Gerüst (≈ 2 Sessions geschätzt)

## Konventions-Anker (Erinnerung)
- Sprachregel 4a: keine „spielen / Spiel / Spieler"
- Leichte Sprache 4b: `pickText()`-Pattern für alle UI-Texte
- iPad 4c: 1180×820 Landscape, 36 px Touch-Ziele, kein Hover-Kleber
- Autosave 7b: Pflicht bei state-relevanten Übergängen
- Keine KI im Produkt: Auftragstexte/Events/Hilfe vorab generieren, statisch ausliefern
- Inbox-Check vor jeder „Fertig"-Meldung
- Commits: `Logistik: <Kurzbeschreibung>`

## Komplexitätshinweis
Atlas schätzt 30–50 Sessions für das Gesamtmodul. Phasenschnitte
sauber halten, nicht den ganzen Brocken auf einmal angehen.

---

## Tagesabschluss 2026-04-20 (00:20)
- Kickoff + Pflichtenheft gelesen
- Empfangsbestätigung + 4 Rückfragen an Atlas verschickt
- Atlas-Antworten gelesen, alle Entscheidungen quittiert
- Routing-Strategie (L1 Luftlinie×1.3 / L2-L3 Hand-Polylines / kein OSRM) bestätigt
- **Nächstes (sobald Gerüst steht):** `balance-matrix.md` → 7 Pflichttests in `test.html` → Headless-Runner-Skelett `runLevel(levelId, seed, strategy)`
- **Blocker:** keiner — wartet auf Atlas-Gerüst (~2 Sessions)
- **Offen für Thomas:** keine akute Frage, alles geklärt

## Tagesabschluss 2026-04-20 (00:55)
- Atlas-Gerüst empfangen (DB, Landing-Card, PHP-Wrapper, Sim-Skelette, Seeds)
- 7 Pflichttests in `test.html` statisch geprüft + via Python-Mathematik verifiziert
  → 22 von 22 Test-Cases bestehen rechnerisch, Browser-Bestätigung steht aus
- **Phase 0 v0.1 geliefert:**
  - `App/sims/logistik/balance-matrix.md` v0.1
  - `App/sims/logistik/headless-runner.html` (Skelett, alles SKIP)
- Atlas-Review angefordert

## Tagesabschluss 2026-04-20 (01:30) — Phase 0 v0.2 nach Atlas-Klarstellung
Atlas hat klargestellt: Phase 0 ist **meine** Arbeit, das Gerüst war
nur Vorarbeit. Konkrete Atlas-Forderungen alle abgehakt:

- **balance-matrix.md v0.2** — Rationale-Spalte je Parameter,
  INSERT-SQLs für `game_levels`, Konsistenz-Checks an Atlas, Event-Wahrsch.
  als Multiplikator umstrukturiert, `noop` zur Strategieliste
- **headless-runner.js** (NEU, vorher .html) — Vanilla-JS-Modul,
  Browser+Node-kompatibel via UMD, Mulberry32-RNG, runLevel-Vertrag,
  4 Strategien (`noop` lauffähig + `naive/greedy/optimal` als Stubs)
- **headless-runner.html** umgebaut zu dünner UI-Hülle (Logik in .js)
- **test.html erweitert** um:
  - Test 8 — Edge-Cases (8 Cases: leere Polyline, getrennter Dijkstra-Graph,
    Pfad zum Selbst, negative Hours, null-Werte, unbekannter Modus)
  - Test 9 — Seeded-Random (4 Cases: Determinismus, verschiedene Seeds,
    Range, Seed 0)
  - Test 10 — Runner-Vertrag (10 Cases: noop läuft, reproduzierbar,
    Stubs werfen kontrolliert)
  - `group()` und Render auf async umgebaut, damit Test 10 (`await
    runLevel`) sauber läuft
- Python-Verifikation der Mathematik: alle neuen Cases grün (10 von 10)
- Atlas-Review erneut angefordert mit aktualisierten Artefakten

**Nächstes (nach Review-OK):** Phase 1 — `engine.js` `tick()` Loop +
Leaflet-Karte mit 13 Seed-Locations
**Blocker:** Atlas-Review + Thomas-Browser-Test (test.html + headless-runner.html)
**Offen für Thomas:** einmal beide HTML-Seiten öffnen, Status melden

## Tagesabschluss 2026-04-20 (02:30) — Phase 1 ausgeliefert
Phase 0 von Atlas mit BESTANDEN reviewt (01:45). Doku-Korrekturen aus
Review umgesetzt (Bahnnetz-Distanzen Paris↔Rotterdam=520, Paris↔München=820;
INSERT-Schema mit `scenario`+`levelNameEasy` in params); 3 Logistik-Level
sind in DB. Phase 1 komplett implementiert:

- **engine.js erweitert:**
  - `loadContent(game, seeds)` — INIT → LOADING_CONTENT → READY
  - `startPlanning(game)` — READY → PLANNING
  - `startSimulation(game)` — PLANNING → RUNNING (paused=false)
  - `pause(game)` / `resume(game)` — User-Pause via paused-Flag
  - `setTimeScale(game, scale)` — 1×/2×/4×/8×
  - `onStateChange(game, callback)` → unregister-Funktion (für Autosave 7b)
  - `tick(game, deltaMs)` — Phase 1: Sim-Zeit + Zeitlimit-Check, alle
    anderen Pipeline-Schritte (Vehicles, Contracts, Economy, Events)
    sind Phase 2/3/5 Stubs
  - `convertRealTimeToGameMinutes(deltaMs, timeScale)` — utility
  - `getSimHoursElapsed(game)` — utility
  - `serialize(game)` / `deserialize(json)` — ohne Listener (Autosave 7b)
  - `createGame(levelNum, {deterministic, seed})` — neue Option für Tests
  - Internes `_setState()` löst onStateChange-Callbacks aus

- **game.html komplett umgebaut:**
  - Leaflet-Karte mit Carto Positron (Atlas-Vorgabe), tap:true für iPad
  - 13 Locations gerendert als CircleMarker pro Typ
    (CAPITAL grün/CITY grau/PORT blau/TERMINAL braun)
  - Layer-Gruppen: Hauptstädte, Städte, Häfen, Terminals, Bahnnetz
  - Layer-Control oben rechts (collapsed:false), Marker-Filter nach
    `visibleFromLevel <= visibleLocationLevel` aus Level-Config
  - Bahnnetz als gestrichelte Polylines (5 Kanten Wien-München-Hamburg-
    Rotterdam-Paris-München)
  - RAF-Loop ruft `tick(deltaMs)` und `updateStatusUI()`
  - Speed-Buttons (1×/2×/4×/8×) mit `aria-pressed`-Toggle, ≥36px
  - Pause-Button mit kontextsensitiven Übergängen
    (READY→PLANNING→RUNNING ↔ pause/resume)
  - Status-Leiste live: Sim-Zeit, Konto, Bilanz, Aufträge x/y, State
  - Save/Load via localStorage (Phase 1 Skelett, Server-API in Phase 7)
  - Autosave-Hook via onStateChange (localStorage)
  - Sprachregel 4a durchgehend („Bearbeiter:in", „Simulation",
    „Durchgang", „Sim-Zeit") — nirgends „Spieler"/„Spiel"

- **test.html erweitert um Test 11 (10 Cases) Phase 1:**
  - 11.1 deterministic createGame → reproduzierbare sessionId+startTime
  - 11.2 State-Übergänge INIT→LOADING_CONTENT→READY
  - 11.3 startPlanning + startSimulation
  - 11.4 tick(1000ms, 1×) → +1 Sim-Min
  - 11.5 tick(1000ms, 4×) → +4 Sim-Min
  - 11.6 paused tick → keine Zeit-Änderung
  - 11.7 50× tick auf zwei Instanzen mit Seed 99 → identische simulationTime
  - 11.8 onStateChange protokolliert alle 4 Übergänge
  - 11.9 Zeitlimit überschritten → state=LEVEL_FAILED
  - 11.10 serialize/deserialize Roundtrip + Listener-Reaktivierung
  - Test 7 (Bahn-Dijkstra) erweitert um Wien→Paris (1220 km)
    und Wien→Rotterdam (1700 km via Hamburg, nicht via Paris)

- **noop-Strategie weiterhin lauffähig:** noop nutzt eigenen Mini-Loop
  (`simHours += 1`), keine engine.tick-Abhängigkeit → läuft ohne
  Phase-2-Implementierung weiter. Test 10 grün-erwartet.

- **Mathematische Verifikation (Python 1:1):** Tick-Konversion,
  Dijkstra-Pfade, Mulberry32 — alle neuen Cases verifiziert.

**Verbliebene Stubs (Phase 2+):** `assignContract`, `calculateRoute`,
`useHint`, `applyMinigameResult`, sowie die Strategien `naive/greedy/optimal`.

**Nächstes (Phase 2):** Auftrags-Generierung + Fahrzeug-Bewegung +
Routen-Visualisierung → Level 1 spielbar
**Blocker:** keiner — Atlas hat „keine weiteren Reviews bis Phase 1
fertig" gesagt, ich starte Phase 2 nach Thomas-Test
**Offen für Thomas:**
- Browser-Test `http://localhost/geograsim/App/sims/logistik/test.html` (Test 1–11, ~36 Cases)
- Browser-Test `http://localhost/geograsim/App/logistik?level=1` (Karte sichtbar, Pause-Button startet Sim-Zeit, Speed-Buttons schalten)

## Tagesabschluss 2026-04-20 (03:30) — Phase 2 ausgeliefert + Header-Fix

Atlas-Phase-1-OK + grünes Licht für Phase 2 (03:00). Atlas hat mein
Phase-1-Plan-§6 1:1 übernommen und „keine weiteren Reviews bis Phase 2
fertig" gesagt. Header-Klassen-Anfrage von 02:45 wurde nicht direkt
beantwortet (Überschneidung), aber das Template ist die Wahrheitsquelle —
Header habe ich selbst aufs Template-Pattern umgestellt (kein Klassen-
Erfinden, nur 1:1-Übernahme).

### Header-Fix (vor Phase 2)
- Klassen auf Template-Muster: `ggs-header-logo` + `ggs-header-logo-icon`
  + `ggs-header-logo-text` + `ggs-header-separator` + `ggs-header-module`
  + `ggs-header-badge` + `ggs-header-spacer` + `ggs-header-actions` +
  `ggs-speed`/`ggs-speed-btn` + `ggs-btn-ghost`
- Pause-Button als `data-speed="0"` integriert (Template-Vorschlag)
- Lehrplan-Link `📚` ergänzt (verweist auf modul-logistik.php#lehrplan-bezug)
- Eigene CSS-Override-Regeln entfernt (DS macht das jetzt sauber)
- Speed-Button-Wahl steuert auch start (von PLANNING → RUNNING bei `>0`)

### Phase 2 — Engine
- `calculateRoute(game, originId, targetId, mode)` — Phase-2-Strategie
  Luftlinie × 1.3, liefert Route mit Geometrie + Distanz + Dauer +
  Segments. Provider markiert als `airline_x1.3` (Phase 3 ersetzt mit
  Hand-Polylines).
- `assignContract(game, contractId, vehicleId)` — Validierung,
  Routenberechnung, Vehicle springt zur Origin (kein Pickup-Drive in
  Phase 2), States werden gesetzt.
- `_updateVehicles(game, simMinutes)` im tick — Routen-Interpolation,
  bei Ankunft Vehicle→IDLE, Contract→DELIVERED
- `_completeContract` — Verspätung berechnen, Erlös +
  Bonus−Fahrkosten−Strafe buchen, Contract in completedContracts,
  Notification, Auto-Folgeauftrag generieren (Phase-3-Vorgriff für
  L1-Spielbarkeit)
- `seedInitialVehicles(game)` — Level-Config-driven Vehicle-Erstellung
  (L1: 1× TRUCK_SMALL in Wien)
- `seedInitialContracts(game)` — Level-1: 1× Wien→Salzburg fest
- `tick` erweitert um Erfolgsprüfung (Time-Limit-Erreichung mit
  minTarget → LEVEL_SUCCESS, sonst LEVEL_FAILED)

### Phase 2 — Headless-Runner
- `naive`-Strategie aktiviert: nimmt erstes OPEN ↔ erstes IDLE,
  tickt 5 Sim-Min/Iter bis state≠RUNNING. Nutzt opts.seeds wenn
  übergeben (für Tests ohne window.LOGISTIK_SEEDS).
- runLevel ruft createGame mit deterministic+seed
- noop bleibt unverändert lauffähig (eigener Loop, keine engine.tick-Abhängigkeit)

### Phase 2 — UI (game.html)
- **Contracts-Panel** zeigt Cards pro Auftrag mit Code, Strecke,
  km, Erlös, Frist, State-Badge. Klick selektiert (toggleable).
- **Vehicles-Panel** zeigt Cards pro Fahrzeug mit Name, Speed, Kapazität,
  Standort. Klick bei selected contract → assignContract.
- **Routen-Polylines** (durchgezogen, dunkelgrün) für jede activeRoute
- **Fahrzeug-Marker** als 🚚/🚆 (DivIcon), Position aus RAF-Loop
- **Hilfestufe BLINK_EXACT** (Phase 5 vorgegriffen): blinkende Ringe um
  Origin (gelb) und Target (rot) bei selected contract
- **Toast-Notifications** bei Delivery (mit Net-Betrag)
- **Sprachregel 4a** durchgehend (Aufträge, Bearbeiter:in, etc.)

### Phase 2 — Tests (test.html)
- Test 12 (5 Cases) — calculateRoute Wien→Salzburg ≈ 326 km × 1.3
- Test 13 (8 Cases) — assignContract State-Übergänge + doppelte Zuweisung wirft
- Test 14 (3 Cases) — tick bewegt Vehicle (segmentProgress strikt zwischen 0/1)
- Test 15 (6 Cases) — Delivery + Erlös + Folge-Auftrag automatisch
- Test 16 (3 Cases) — latePenaltyOverride wird respektiert + analytics++
- Test 17 (5 Cases) — naive auf L1 schafft Akzeptanzkorridor (≥4 Aufträge,
  Profit ≥ +1000€, success=true)
- Test 10.3 angepasst: greedy wirft jetzt „Phase 2", naive ist aktiv

### Mathematische Verifikation (Python 1:1)
- Wien-Salzburg airline = 250.82 km, × 1.3 = 326.06 km, 4.66 h @ 70 km/h
- Pro Auftrag: Net 792.27€ (1300€ Erlös − 507.73€ Fahrkosten)
- 24h / 4.66h = 5 Aufträge schaffbar
- Gesamtprofit: 3961€ > 3000€ Min-Ziel → success ✓

**L1 ist spielbar.** Im Browser: Klick auf Auftrag (blinkende Ringe) →
Klick auf LKW → Fahrt beginnt → 1× oder 4×/8× Speed → LKW fährt nach
Salzburg → Toast „Auftrag abgeschlossen +792€" → Folge-Auftrag erscheint.

**Nächstes (Phase 3):** Mehrfahrzeuge, Mehraufträge mit Variation,
Kostenmodell für Miete/Standkosten, Hand-Polylines für 12 Hauptstrecken,
Level 2 spielbar. Ziel: ~50% schaffen L2 mit greedy, ~70% mit optimal.

**Blocker:** keiner — Atlas hat „direkt Level 1 spielbar → Thomas-
Browsertest → Phase 3" gesagt. Header-Klassen-Frage 02:45 ist faktisch
durch Selbstkorrektur auf Template gelöst, aber Atlas könnte noch
beanstanden falls anders gemeint.

**Offen für Thomas:**
- Browser-Test `App/logistik?level=1`: 1 Auftrag links, 1 LKW rechts,
  klicken → blinkende Ringe → LKW klicken → Fahrt → Toast bei Ankunft →
  Folge-Auftrag → Bilanz wächst
- Test-Harness `App/sims/logistik/test.html`: 17 Gruppen, ~60 Cases,
  alle grün erwartet

## Tagesabschluss 2026-04-20 (05:00) — Phase 3 ausgeliefert

Atlas-Wrapper-Patch war 04:00 fertig, Thomas hat Phase 2 live gesehen
und sofort gefragt, ob die LKW Hauptstraßen statt Luftlinie fahren
können — exakt Phase 3 nach Plan. Grünes Licht erteilt, gestartet.

### Phase 3 — Hand-Polylines + Engine
- **`App/assets/data/lg-routes-osm.json`** — 17 Hauptstrecken zwischen
  den 13 Locations mit handgepflegten Polyline-Stützpunkten an
  bekannten Knoten (St. Pölten, Linz, Brennerpass, Lille, Reims, etc.).
  Bidirektional. Polyline-Längen via Python verifiziert.
- **`engine.calculateRoute` Phase 3** — Polyline-Lookup (Forward +
  Reversed), Fallback auf Luftlinie × 1.3. Provider-Markierung
  (`osm_polyline` / `osm_polyline_reversed` / `airline_x1.3`).
- **Pickup-Drive** in `assignContract`: wenn Vehicle nicht am Origin,
  zweistufige Route (Pickup + Delivery)
- **4-Phasen-Trip** in `_updateVehicles`: pickup_drive → loading →
  delivery_drive → unloading. Mit Phase-Übertrag-Loop (kein Drift bei
  großen Ticks)
- **`_completeContract`** mit `vehicle`-Param: noEmpty-Bonus korrekt,
  Pickup-Fahrkosten zusätzlich, `contract.pickupKm` für Tracking
- **Kostenmodell** im tick (`_applyRunningCosts`): Standkosten/h für
  IDLE-Vehicles, Miete/Tag für alle Vehicles
- **Auftrags-Variation** deterministisch (Mulberry32 + Counter):
  12 `CONTRACT_TEMPLATES`, L2/L3 ziehen daraus, L1 hardcoded auf
  **Wien↔Salzburg-Pendel** (vermeidet Leerfahrten, hält naive im
  Akzeptanzkorridor)
- **`seedInitialVehicles`** verteilt auf Hubs (L1: Wien; L2: Wien/
  München/Hamburg; L3: 5 Hubs)

### Phase 3 — Headless-Runner
- **`greedy`-Strategie aktiviert**: höchster rewardBase ↔ größtes
  Vehicle das passt

### Phase 3 — Tests
5 neue Gruppen, 19 neue Cases (Test 18-22). Total **22 Gruppen / ~80 Cases**.

### Mathematische Sanity-Checks (Python)
- L1 Pendel mit airline-Fallback: 4 Trips × 792€ = 3169€ > 3000€ Min ✓
- L1 mit Polyline (kürzer): noch profitabler
- L2/L3 Akzeptanzkorridor: nicht erfüllt (siehe Balance-Hinweis unten)

### Atlas-Wartepunkt
Wrapper-Erweiterung in `logistik.php` für `lg-routes-osm.json`
(siehe Atlas-Mail 2026-04-20-0500). Bis dahin fahren LKWs im Browser
noch auf Luftlinie × 1.3. Test-Harness unbetroffen (Inline-Seeds).

### Balance-Hinweis (für künftige Tuning-Iteration)
L2/L3-Akzeptanzkorridor mathematisch noch nicht erfüllt. Pflichtenheft
gibt `rewardBase=1000` fix (unabhängig von Container/Distanz). Mit
Pickup-Drive werden lange Strecken (>500km) verlustig. Tuning für
Phase 4+ (z.B. Reward proportional zur Distanz). Test 22 prüft nur
strukturelle Lauffähigkeit.

### Offen für Thomas
- Atlas-Wrapper-Update abwarten, dann Browser-Test L1 (Pendel-Modus,
  Polyline-Routen sichtbar — LKW fährt entlang St. Pölten-Linz-Wels
  statt diagonal durch die Donau)
- Test-Harness `test.html`: 22 Gruppen / ~80 Cases sollten alle grün

## Tagesabschluss 2026-04-20 (06:00) — Phase 4a + Music

### Music-Player aktiv
- 4 Tracks aus `.humanInput/Background Music/_claimed/` kopiert nach
  `App/sims/logistik/sounds/music/`:
  - `saddlewood-metronome.mp3` (3 MB, mechanisch-rhythmisch)
  - `gears-and-marbles.mp3` (3.2 MB, Steve-Reich-Stil)
  - `barbed-lullaby.mp3` (3.2 MB)
  - `untitled-instrumental.mp3` (2.9 MB)
- `.ggs-music`-Markup im Header eingebaut (Pattern aus template.html)
- `ggsMusicSetup`-Funktion inline (gleicher Code-Pfad wie Klima/Fluss)
- Default-Volume 22 %, localStorage `ggs-music-logistik`

### Phase 4a — Engine
- **TRAIN-Routing**: `calculateRoute` nutzt für mode=TRAIN den
  `railShortestPath`-Dijkstra über `worldState.railnet`. Wenn beide
  Locations Bahnknoten sind, kommt eine echte Mehr-Segment-Route
  raus (provider: `rail_dijkstra`), sonst Fallback auf
  Polyline/airline. Geometrie führt durch alle Bahnknoten.
- **Hafen-Schiffsankünfte**: `_maybeGenerateShipArrival(game)` läuft
  im tick, alle 6 Sim-Stunden ein Schiff (deterministisch via Counter).
  Generiert OPEN-Auftrag mit Origin=Hafen, Target=Hauptstadt-Pool
  rotierend. Code-Präfix `SHIP-`, `narrativeText` erwähnt Hafen,
  Notification `SHIP_ARRIVED`. Nur aktiv bei `cfg.portsEnabled=true`.
- **Container-Standkosten**: `_applyRunningCosts` rechnet zusätzlich
  zu Vehicle-Standkosten/Miete auch `containerStandCostPerHour ×
  quantityContainers × simHours` für jeden OPEN-Auftrag mit
  Origin am Hafen.

### Phase 4a — Tests (3 neue Gruppen, ~10 Cases)
- Test 23 (5): TRAIN-Routing über railnet-Dijkstra (Wien→Hamburg
  via München = 1200 km, 2 RAIL-Segmente), Fallback wenn kein
  Bahnknoten
- Test 24 (4): Schiffsankunft-Generator (nach 7h Sim mit
  portsEnabled → SHIP-Auftrag + Notification)
- Test 25 (1): Container-Standkosten an Häfen (3 Container × 10€/h
  × 1h = 30€ pro Sim-Stunde abgezogen)

Total jetzt: **25 Test-Gruppen, ~90 Cases**.

### Phase 4b verschoben (Intermodal)
Vollständige intermodale Aufträge mit `legs[]`-Struktur und Multi-
Vehicle-Übergabe (LKW→Zug→LKW) sind komplexer als die anderen 3
Phase-4-Punkte zusammen. Phase 4a deckt die Basis-Strukturen ab
(Bahn-Routing + Hafen-Aufträge), 4b kommt als nächste Iteration:
- Contract-Schema um `legs: [{mode, originId, targetId}]` erweitern
- Engine-Logik für Übergabe zwischen Vehicles am Terminal/Hafen
- Container am Hafen warten auf Folge-Vehicle (Inland-Strecke)
- LOADING-Phase am Übergabepunkt mit echten Cargo-Transfer-Zeiten
- Lehrziel #13 (kombinierte Verkehre) wirklich erlebbar

### Offen für Thomas
- Browser-Test [App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1):
  Music-Player rechts oben sollte sichtbar sein (Tracks wählbar, ▶ startet)
- Browser-Test [test.html](http://localhost/geograsim/App/sims/logistik/test.html):
  25 Gruppen / ~90 Cases sollten alle grün
- Phase-4b-Entscheidung: jetzt oder erst nach L3-Tuning?

### Atlas-Wartepunkte
- Wrapper-Erweiterung `lg-routes-osm.json` (von Mail 0500, noch offen)
- Music-Tracks-Bestätigung (von Mail 0540, „letzten paar" akzeptiert?)

## Tagesabschluss 2026-04-20 (06:30) — Phase 4b + 5a ausgeliefert

Thomas: „Arbeite weiter, soweit du kannst." → Phase 4b (Intermodal-Engine)
und Phase 5a (Event-Engine) komplett implementiert. Phase 5b
(Hilfestufen-UI) auf Phase 6 verschoben — passt natürlich zu
Sprachregel-Check + Leichte Sprache (alles UI-Polish).

### Phase 4b — Intermodale Aufträge

- **Schema**: Contract bekommt optional `intermodal: true` und
  `legs: [{legNum, mode, originLocationId, targetLocationId, state,
  assignedVehicleId, routeId, departTime, arrivalTime}]`. Plus
  `currentLegNum` und `currentLocationId` (wo der Container gerade ist).
- **`_makeIntermodalContract(game, code, legs, containers, isExpress)`**:
  Factory mit Aggregations-Daten (Gesamt-Distanz approx, Gesamt-Frist,
  rewardBase × 1.5 als Intermodal-Bonus).
- **`assignContract(game, contractId, vehicleId, legNum?)`**:
  - bei intermodal: `legNum` Pflicht (sonst nimmt currentLegNum)
  - prüft Vehicle-Mode-Match zum Leg-Mode (TRAIN-Leg → TRAIN-Vehicle)
  - setzt leg.state statt c.state
- **`_completeLeg(game, contract, legNum, route, vehicle)`**:
  - markiert Leg als DELIVERED
  - bucht Fahrkosten dieses Legs sofort (Reward kommt am Schluss)
  - setzt currentLegNum/currentLocationId vor
  - aktiviert nächsten Leg auf OPEN (Notification `LEG_TRANSFERRED`)
  - bei letztem Leg: Reward + Bonus + Strafe in einem buchen,
    Contract als DELIVERED/LATE in completedContracts
- **`_stepVehiclePhase` UNLOADING**: erkennt intermodal und ruft
  `_completeLeg` statt `_completeContract`

**Wichtig**: Strategien (naive/greedy) sind Phase 4b NICHT angepasst —
sie würden auf intermodal-Aufträgen mit Mode-Mismatch crashen. Phase 4c
oder Phase 5 für Strategy-Anpassung. UI ebenfalls noch nicht
intermodal-tauglich; Tests testen explizit via _makeIntermodalContract.

### Phase 5a — Event-Engine

- **`_maybeGenerateEvents(game, simMinutes)`**: würfelt deterministisch
  via Mulberry32 (`game.seed ^ 0xE7E7` + Counter `_eventsRngCalls`).
  Pro Sim-Stunde-Block + EVENT_TYPE: `prob = baseProb × eventProbabilityMultiplier`
- **EVENT_RULES** (aus PH 65.5): TRAFFIC_ACCIDENT 5 % Speed×0.5,
  SNOW 10 % Speed×0.7 (Alpen), PORT_DELAY 8 % Loadtime×1.5
- **Event-Dauer**: 30-120 min (deterministisch via 2. Würfel)
- **`_expireEvents(game)`**: filtert abgelaufene Events
- **`_vehicleSpeedMultiplier(game)`**: kombiniert alle aktiven Events
  multiplikativ → wirkt im `_stepVehiclePhase` auf `route.totalDurationMinutes`
- **Notifications**: `EVENT_STARTED` mit Emoji-Message (🚧 Unfall,
  ❄ Schnee, ⚓ Hafenüberlastung)

### Phase 5b — Hilfestufen-UI verschoben auf Phase 6

`contract.hintMode` ist schon im Schema (Phase 0/2), `BLINK_EXACT` ist
visuell umgesetzt (Phase 2). Die anderen 4 Stufen (`SHOW_COUNTRY`,
`SHOW_REGION`, `DISTANCE_FEEDBACK`, `NONE`) brauchen UI-Erweiterung
und passen natürlich zu Phase 6 (UI-Polish + Sprachregel + Leichte
Sprache) — alle drei drehen an der Darstellungs-Schicht.

### Phase 4b + 5 — Tests

5 neue Cases:
- Test 26 (4 Cases): Intermodal-Schema (legs[], States)
- Test 27 (8 Cases): Multi-Leg-Trip (Rotterdam_Hafen→Hamburg→München)
- Test 28 (3 Cases): Event-Generierung deterministisch + reproduzierbar
- Test 29 (1 Case): Manuelles Event reduziert Vehicle-Speed
- Test 30 (2 Cases): Events laufen nach Dauer ab

Total jetzt: **30 Test-Gruppen, ~110 Cases**.

### Was noch offen ist (Phase 6, 7, weitere)

- Phase 6: Hilfestufen-UI, 1× Minigame (An-die-Rampe-Einparken),
  Sprachregel-4a-Check aller UI-Texte, Leichte-Sprache via `pickText()`,
  Glossar-Begriffs-Anbindung
- Phase 7: Lehrkraft-Konfiguration (über Admin-Tool), Analytics-Dashboard
  (Kennzahlen aus PH 23.2)
- Strategie-Anpassung: greedy für intermodal, optimal-Strategie
- Balance-Tuning für L2/L3 (rewardBase distanzproportional?)

### Browser-Tests offen
- Test-Harness: 30 Gruppen / ~110 Cases sollten alle grün
- Live-UI: Music-Player + LKW-Polylines (nach Atlas-Wrapper-Update)

## Tagesabschluss 2026-04-20 (07:00) — Phase 6 ausgeliefert

Thomas: „Mach weiter. Direkt Phase 6 machen." → UI-Polish komplett in 3 Subphasen.

### Phase 6a — Sprachregel + pickText + Hilfestufen + Glossar + Events
- **Sprachregel-4a-Audit**: alle UI-Texte sauber (nur interne Doc-Comments
  enthalten „Spielzustand"/„spielbar" — laut Atlas-Briefing erlaubt)
- **pickText-Helper** mit Easy-Detection: `?easy=1` URL-Param oder
  `localStorage 'logistik:easy'='1'` schaltet auf Leichte-Sprache-Variante
- **GLOSSAR-Fallback** (Pattern aus Fluss): 16 Begriffe inline definiert
  (Container, Intermodal, Umschlag, Luftlinie, Disposition, Frist,
  Leerfahrt, Standkosten, Bahnterminal, Hafen, Routing, Spedition,
  Logistikkette, Auftrag, Fahrzeug, Logistik). Wenn Atlas/Glossar
  später DB-API liefert → Fallback bleibt als Backup.
- **Glossar-Click-Handler**: `.ggs-glossar-popup` Pattern aus Design-System
- **Glossar-Markups** in Auftrags-Karten + Hint-Texten + Didaktik-Text
- **5 Hilfestufen visuell** (PH 10.3):
  - BLINK_EXACT: ✓ Origin (gelb) + Target (rot) blinken (war Phase 2)
  - SHOW_COUNTRY: alle Locations im selben Land wie Target leuchten
  - SHOW_REGION: alle Locations in selber Region leuchten (sanftes Blau)
  - DISTANCE_FEEDBACK: Skelett (UX-Mechanik kommt mit Phase 7+)
  - NONE: nichts angezeigt
- **Event-Panel** im Didaktik-Bereich: aktive Events als Pills
  („🚧 Unfall · noch 25 min", „❄ Schnee · noch 60 min")
- **pickText in Toasts**: standard + easy für Auftrag-Zuweisung,
  Delivery-Result, Fehlermeldungen
- **CSS für `[data-glossar]`**: dotted-Underline + cursor:help

### Phase 6b — Intermodal-UI
- **Auftrags-Karte** erkennt `c.intermodal` und zeigt **Leg-Liste** als
  Pills (durchgestrichen=DELIVERED, fett-grün=OPEN, grau=WAITING)
- **🔗-Symbol** im Auftrag-Titel bei Intermodal
- **Mode-Filter im Vehicles-Panel**: bei selected intermodal-Auftrag werden
  Vehicles, deren Mode nicht zum aktuellen Leg passt, als „busy" angezeigt
  mit Hint „benötigt: 🚆 Zug" / „benötigt: 🚚 LKW"
- **Click-Handler** übergibt `currentLegNum` an `assignContract`
- **Bei Intermodal**: Auftrag bleibt selected nach Vehicle-Wahl (für
  Folge-Legs)
- **Demo-Hook**: `?demo-intermodal=1` triggert Beispiel-Auftrag
  Rotterdam_Hafen → Hamburg → München bei L3
- **Strategien angepasst**: naive/greedy filtern intermodal-Aufträge
  raus (Phase 6c oder Tuning-Iteration für intermodal-fähige Strategien)

### Phase 6c — Minigame „An-die-Rampe-Einparken"
- **Engine: `applyMinigameResult(game, result)`** implementiert (war Stub):
  - Speichert `loadingTimeFactor` aus `result.metadata` in
    `game._loadingTimeModifier`
  - Wirkt auf nächste LOADING-Phase (in `assignContract`),
    danach Reset auf 1
  - Trackt `analytics.minigamesPlayed` + `minigamesSuccessful`
  - Generiert `MINIGAME_DONE`-Notification
- **UI: Modal mit Canvas** (420×200), Top-Down-View
- **Steuerung**: Touch-Buttons ⬅ ➡ + 📦 Andocken (iPad-tauglich, ≥44px)
- **Bewertung**:
  - LKW-Vorderkante in 12 px um Rampen-Mitte → perfect → factor 0.8 (−20 %)
  - 12-30 px → ok → factor 1.0
  - >30 px → miss → factor 1.3 (+30 %)
- **PH 65.9 Werte exakt umgesetzt**
- **Trigger-Button 🎮 im Header** sichtbar wenn `cfg.minigamesEnabled=true`
  (L2/L3)
- **pickText** für alle Minigame-Texte (standard + easy)

### Phase 6 — Tests
- Test 31 (4 Cases): applyMinigameResult setzt Modifier, wird in
  nächster LOADING-Phase angewendet (5 × 0.8 = 4 min), dann Reset
- Test 32 (1 Case): Minigame-Fail erhöht Loading auf 6.5 min (5 × 1.3)

Total jetzt: **32 Test-Gruppen, ~115 Cases**.

### Was Phase 6 NICHT enthält
- Strategien-Anpassung für intermodal (Phase 6d / Tuning)
- DB-Glossar-API (Atlas + Glossar-Instanz)
- DISTANCE_FEEDBACK voll umgesetzt (braucht Klick-zum-Wählen-UX)
- Keine API-Endpunkte
- Kein Lehrkraft-Konfig-UI (Phase 7)

### Browser-Tests offen
- [test.html](http://localhost/geograsim/App/sims/logistik/test.html):
  32 Gruppen / ~115 Cases
- [App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1):
  Glossar-Begriffe (gepunktete Unterstreichung) anklickbar; Music-Player
- [App/logistik?level=2](http://localhost/geograsim/App/logistik?level=2):
  + Minigame-Button 🎮 im Header
- [App/logistik?level=3](http://localhost/geograsim/App/logistik?level=3):
  + Bahn-Layer + Häfen + Schiffsankünfte + Events sichtbar im Panel
- [App/logistik?level=3&demo-intermodal=1](http://localhost/geograsim/App/logistik?level=3&demo-intermodal=1):
  + intermodaler Demo-Auftrag mit 2 Legs (LKW Rotterdam_Hafen→Hamburg, Zug Hamburg→München)
- [App/logistik?level=1&easy=1](http://localhost/geograsim/App/logistik?level=1&easy=1):
  Leichte-Sprache-Modus aktiv

### Atlas-Wartepunkte
- Wrapper-Erweiterung `lg-routes-osm.json` (Mail 0500)
- Glossar-Begriffe (Anfrage 0010, Atlas koordiniert)
- Music-Tracks-Bestätigung (Mail 0540)

## Tagesabschluss 2026-04-21 (Phase 8) — Multi-Contract-Tour

Thomas's Konzept übernommen: **Vehicle-first-Lademodus**.
1. Klick auf IDLE-Vehicle → Lademodus aktiv (Banner + Laderaum-Slots)
2. Klick auf OPEN-Auftrag → wandert in den LKW (Cargo-Emoji im Slot)
3. „🚀 Fahrt starten" → Multi-Stop-Tour beginnt

**Engine-API neu:**
- `loadContract(game, vehicleId, contractId)` — Kapazität-Check, Mode-Check, Contract → RESERVED
- `unloadContract` / `cancelLoading` — vor Fahrt-Start
- `startTour(game, vehicleId)` — baut Stops (alle Pickups erst, dann alle Dropoffs in Klick-Reihenfolge), konsolidiert aufeinanderfolgende Stops am gleichen Ort, berechnet Routen pro Segment, startet erste Phase

**Trip-Phase neu:**
- `tour_driving` (zwischen Stops) und `tour_handling` (an einem Stop laden/entladen)
- `_stepVehiclePhase` durchläuft Stops sequentiell, bucht Fahrkosten pro Segment sofort, Reward bei Dropoff
- `_endTour` setzt Vehicle zurück auf IDLE

**Cargo-Typen mit Emojis:**
- `lg-cargo-types.json` um `emoji`-Feld erweitert (📱 Elektronik, 👕 Kleidung, ⚙️ Maschinen, 🥫 Lebensmittel, 🧪 Chemie, 🔩 Fahrzeugteile, 🪵 Holz/Papier, 🪨 Rohstoffe)
- `CONTRACT_TEMPLATES` haben jetzt `cargoTypeIds`-Liste pro Strecke
- `_nextContractTemplate` zieht deterministisch einen Cargo-Typ
- Laderaum-Slots zeigen das Cargo-Emoji des belegenden Auftrags

**UI-Lademodus:**
- Vehicle-Card zeigt Banner + Laderaum-Grid (cap-viele Slots, gefüllt mit Cargo-Icons)
- „🚀 Fahrt starten"-Button (disabled bei leer) und „↺ Abbrechen"
- Klick auf RESERVED-Auftrag macht nichts (Card ist busy-styled)
- Wechsel zu anderem Vehicle: altes Vehicle wird automatisch entladen (cancelLoading)

**Bestehende Pfade unangetastet:**
- `assignContract` bleibt für intermodale Aufträge (selectedContractId-Pfad)
- Engine-Tests 13–17 nutzen weiter assignContract → laufen unverändert
- Intermodal hat eigenen 4-Phasen-Trip (pickup_drive/loading/delivery_drive/unloading), nicht den neuen Tour-Pfad

**Was Phase 8 NICHT enthält (Phase 9 / nice-to-have):**
- Tour-Route-Vorschau auf der Karte vor „Fahrt starten" (geplante Stops als gestrichelte Linie)
- Klick-Animation „Container wandert in den LKW"
- Naive/greedy-Strategien wurden noch nicht angepasst (filtern intermodal+RESERVED)
- Optimierungs-Button („Reihenfolge optimieren ✨")
- Während-der-Fahrt-Nachladen (an Stops zusätzliche Aufträge dazunehmen)

**Browser-Test:**
- [http://localhost/geograsim/App/logistik?level=3](http://localhost/geograsim/App/logistik?level=3)
- Zug-Card klicken → Banner „Lademodus aktiv" + 20 Slots
- Aufträge nacheinander reinklicken — Container füllen sich mit Emoji
- „🚀 Fahrt starten" — Zug fährt erst alle Pickup-Orte, dann alle Dropoff-Orte ab
- Bei selber Origin: Stops werden konsolidiert (1 Pickup für mehrere Aufträge)

## Tagesabschluss 2026-04-20 (08:00) — Phase 7a: Analytics + Balance-Tuning

Thomas: „mach weiter" → Phase 7 angepackt. Teile, die Plattform-Domain
sind (Lehrkraft-Admin + API-Endpunkte + DB-Migration), an Atlas
übergeben (Mail 0800). Modul-Seite von Phase 7 ist fertig:

### Balance-Tuning: rewardBase distanzabhängig
- `_computeReward(distanceKm, isExpress)` in Engine
- PH-Base + `max(0, (km - 300) × 1.5)` Zuschlag
- Kurze Strecken bleiben PH-kompatibel (Wien-Salzburg: 1000→1039€)
- Lange Strecken profitabel (Wien-Hamburg: 1000→2350€)
- Intermodal-Bonus × 1.5 bleibt erhalten
- Python-verifiziert: L1-Pendel-naive bleibt schaffbar (845€ × 4 = 3380€)

### Analytics-Tracking in Engine
- `totalRewardEarned`, `totalPenaltyPaid`, `totalFareCostPaid`
- `runningCostsTotal` (war schon da)
- `contract.durationHours` pro abgeschlossenem Auftrag

### UI — Analytics-Panel
- Toggle-Button 📊 oben rechts auf der Karte
- 12 Kennzahlen live (Aufträge, Leerfahrtenquote, Verspätungsquote,
  Ø-Net/Auftrag, Erlöse, Fahrkosten, Strafen, Standkosten+Miete,
  Minigame-Erfolg, Bilanz/Profit)
- Live-Update im RAF-Loop bei offenem Panel

### UI — End-Screen
- Overlay bei State-Wechsel zu LEVEL_SUCCESS / LEVEL_FAILED
- Success in grün, Fail in rot
- Gekürzte Stats-Liste (5 wichtigste Werte)
- pickText für standard/easy
- „Neuer Durchgang"-Button reloaded Seite
- Via `onStateChange`-Hook automatisch getriggert

### Was bei Atlas liegt (Phase 7b)
- Lehrkraft-Admin-Tool (admin-levels.html erweitern vs. separat)
- API-Endpunkte `App/php/api/logistik-*.php` — ich kann selbst, erwarte Go
- DB-Migration `lg_contracts_log` — Freigabe vom Schema
- Glossar-API-Anbindung falls die 13+ Begriffe angelegt sind

### Offen für Thomas
- Browser-Test L1/L2/L3: Analytics-Button 📊 rechts oben, End-Screen
  nach Zeitlimit oder Min-Ziel-Erreichen
- Die Balance-Tuning-Änderung macht L1 leichter, L2/L3 besser spielbar

## Tagesabschluss 2026-04-20 (07:30) — UX-Iteration „Fahrzeug-Auftrags-Verbindung"

Thomas: „wenn der Auftrag angenommen wurde und das Fahrzeug unterwegs
ist, wäre es schön, wenn irgendwo erkennbar wäre, dass diese Verbindung
wirklich vorliegt." → drei Mechanismen ineinandergreifen lassen.

### Was neu ist
- **Vehicle-Card zeigt Trip-Info** bei MOVING/LOADING/UNLOADING:
  - Phase-Label („🟡 Leerfahrt zum Abholen", „📦 Lädt", „🟢 Unterwegs",
    „📥 Lädt ab")
  - Auftrag-Code + Strecke („C-001 · Wien → Salzburg")
  - Progress-Bar bei Fahrten (live aus `v.segmentProgress`)
  - ETA („ETA 14:32") oder Restdauer („noch 3 min") je nach Phase
- **Vehicle-Marker mit Hover-Tooltip** (Leaflet `bindTooltip`, sticky):
  Name, Phase, Auftrag, Prozent-Fortschritt
- **Klickbares Pairing**:
  - Klick auf Vehicle-Card oder -Marker → selectedVehicleId
  - Zugehörige Contract-Card bekommt `linked-pair`-Highlight (blauer Ring)
  - Vehicle-Card bekommt `linked`-Highlight (grüner Ring)
  - Zugehörige Route wird auf der Karte dicker+kräftigrot, andere Routen
    werden gedimmt
  - Klick zentriert die Karte auf's Fahrzeug
  - Erneuter Klick deselect
- **Auto-Deselect**: wenn Vehicle IDLE wird (Delivery fertig) → Selection
  automatisch gelöscht

### CSS-Klassen neu
- `.lg-trip-info` (+ `.pickup`, `.loading`) — Trip-Box unter Vehicle-Titel
- `.lg-progress-bar` + `.lg-progress-fill` (+ `.pickup` für gelb)
- `.lg-card.linked` (grün) + `.lg-card.linked-pair` (blau)

### Keine neuen Tests
UX-Erweiterung (visuell), kein Engine-Change. Test-Gruppen bleiben bei 32.

### Offen für Thomas
- Browser-Test [App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1):
  - Auftrag klicken → LKW klicken → Fahrt startet
  - Jetzt: Vehicle-Card zeigt „🟢 Unterwegs · C-001 · Wien → Salzburg · ETA ..."
    mit Progress-Bar die mit 1×/4×/8× Speed füllt sich live
  - Marker-Hover auf der Karte zeigt Tooltip
  - Marker-Klick zentriert Karte + beide Cards sind farblich verbunden
- Bei L2/L3 analog — mit mehreren aktiven Trips siehst du die Fokus-
  Route hervorgehoben, andere gedimmt

## Tagesabschluss 2026-04-22 — UX-Polish-Sprint + Atlas-Phase-7b-Entscheidungen

### Neue Atlas-Nachricht (1200, gelesen + quittiert)
`2026-04-22-1200-phase7-entscheidungen-und-db.md` — Atlas hat nach
2 Tagen Pause alle drei offenen Punkte entschieden:
1. **Lehrkraft-Admin**: Atlas erweitert `App/admin-levels.html` (zentrales Tool).
   **Mein Beitrag:** `App/sims/logistik/admin-fields.json` mit
   Feld-Beschreibungen aus balance-matrix.md §5.
2. **API-Endpunkte**: Ich baue selbst, Pattern aus `App/php/api/glossar.php`.
   Drei Endpunkte: `logistik-sessions.php`, `logistik-saves.php`,
   `logistik-analytics.php`. Security: Session-ID gegen `student_sessions`
   validieren. Kein formales Review, ich committe wenn zufrieden.
3. **DB-Migration `lg_contracts_log`** erledigt. Schema erweitert um
   `intermodal`, `route_distance_km`, `route_mode`. Meister pingt Atlas
   für Produktions-Replikation.
4. Glossar-API: Fallback bleibt, Switch wenn Glossar liefert. Pattern
   kommt in `App/docs/module-interface.md`.

Thomas-Frage „nächstes Modul" → Atlas sagt: erst nach Phase 7b.

### Phase 8 + UX-Iterationen (heute + gestern)
- **Multi-Contract-Tour**: Vehicle-first-Lademodus, Cargo-Icons (📱👕⚙️🥫🧪🔩🪵🪨),
  Klick-Reihenfolge-Tour (Pickups zuerst, dann Dropoffs; konsolidiert
  aufeinanderfolgende Stops am gleichen Ort), Laderaum-Slot-Visualisierung,
  „🚀 Fahrt starten" / „↺ Abbrechen"
- **Container-Visualisierung**: bis 6 Container als einzelne 📦-Symbole,
  ab 7 als „N× 📦". Mit Glossar-Popup (TEU + metrische Maße)
- **Scale-Bar** auf Leaflet-Karte (unten-links, metrisch, bis 100 km)
- **Pro-Vehicle-Farben** (8 Palette-Farben): Vehicle-Card-Dot, Marker-Ring,
  Route-Farbe (durchgezogen Lieferfahrt, dünn gestrichelt Pickup-Leerfahrt)
- **Event-Symbole** auf der Karte: 🚧 Unfall (Mittelpunkt einer aktiven
  Route), ❄ Schnee (Alpen-Region ±0.8°), ⚓ Hafenüberlastung (am Hafen)
- **Rote Routen bei Verspätung** (dashArray, überschreibt Vehicle-Farbe)
- **Analytics-Panel gedämpft** (1 Hz statt 60 Hz), Status-Leiste 4 Hz
- **Farbleitsystem**: OPEN grün · PICKUP/LOADING/IN_TRANSIT orange ·
  DELIVERED blau · LATE rot · WAITING beige. Deutsche Labels mit pickText
- **State-Labels deutsch**: „offen"/„Abholung läuft"/„unterwegs"/„geliefert"/...
- **Vehicle-Auftrag-Verbindung**: Klick auf Vehicle (trip-aktiv) oder
  Marker → Fokus-Route rot-dick, andere gedimmt, passende Contract-Card
  mit blauem Ring (`linked-pair`). Vehicle-Card mit Progress-Bar + ETA
- **Analytics-Panel + End-Screen** (Phase 7a)
- **Balance-Tuning** (Phase 7a + 8): `_computeReward` distanz-proportional
  + Container-Komponente (+250 €/zusätzl. Container), CONTRACT_TEMPLATES
  auf mehr 1-Container-Ranges (damit kleiner LKW nützlich wird)
- **Drei-Kommastellen-Fix**: `fmtEuro()` + `fmtEuroSigned()` runden
  überall auf ganze Euro (Status, Toast, Analytics, End-Screen)

### Minigame-Komplettumbau (mehrere Iterationen heute)
- **Grüne Zielzone** (LKW-Größe + 3 px Toleranz), sichtbar schraffiert mit
  „🎯 Hier parken"-Beschriftung
- **Referenz-Miniaturen** (3× 130×70 Canvas): ✅ Rückwärts · ❌ Vorwärts · ❌ Schief
- **Countdown 3:00** rückwärts, MM:SS-Format, warnt bei <60 s orange, <30 s rot
- **Auto-Andocken** sobald 0,3 s stabil in grüner Zone + rückseitig + langsam
- **Rückseitig-Pflicht**: Nur angle ≈ 180° (Heck zur Rampen-Rückwand) zählt als „richtig"
- **Live-Dock-Status**: „✓ Zone · ✗ Heck zur Rampe (45°) · ✓ still"
- **Crash-Szenarien** → Game Over, factor 1.5:
  - Anderes Fahrzeug rammen
  - Bucht-Wand mit v>55 UND tiefem Overlap
  - Zeitablauf (3 min)
- **Active Mover** ab L2 sichtbar im Feld: fahrendes Fahrzeug mit Waypoints
  + sanfter Lenkung (Math.atan2 + max-turn-rate), wraps am Rand
- **Zeit → Ladezeit-Faktor**: <15 s → 0.7× / 15-30 s → 1.0 / 30-60 s → 1.2 / 60-180 s → 1.3 / Crash/Timeout → 1.5
- **LKW-Optik** verbessert: Kabine + Auflieger, Frontscheibe, Scheinwerfer,
  6 Räder mit gelenkten Vorderrädern, Schatten
- **Web-Audio-Sounds** (synthesized, kein externer File nötig):
  - Engine-Brummen: Sägezahn 55-200 Hz, Volume skaliert mit v
  - Brems-Quietschen: Bandpass-Rauschen 0.25 s Burst bei abruptem v-Abfall
  - Crash: tieffrequenter Sinus-Sweep + Lowpass-Impuls
- **Musik-Auto-Start**: Hauptmusik wird getriggert wenn Modal öffnet
  (click auf #ggs-music-toggle falls nicht playing)
- **Buttons**: „↺ Neu starten" (gelb-warm, 46 px hoch) + „Fertig · Schließen"
  (dunkelgrün), beide groß und touch-tauglich
- **Andocken-Button entfernt** (Auto-Detection ersetzt Manual-Check)

### Offene Arbeitsliste (Phase 7b — Atlas-Reihenfolge)
1. **`App/sims/logistik/admin-fields.json`** — Feld-Beschreibungen
   aus balance-matrix.md §5 (15 Min Arbeit)
2. **`App/php/api/logistik-sessions.php`** — POST start/end, GET status
3. **`App/php/api/logistik-saves.php`** — POST save, GET load
   (gegen `game_saves.save_data` JSON, Engine `serialize()` schon bereit)
4. **`App/php/api/logistik-analytics.php`** — POST in `lg_contracts_log`
   (Schema Atlas bereits angelegt)
5. **Analytics-Logging in `_completeContract`-Hook** — pro Delivery
   POST an Analytics-API

### Nicht-Blocker-Wartepunkte
- Wrapper-Erweiterung `lg-routes-osm.json` (Mail 0500) — Atlas hatte
  bestätigt 0400 als Tile-Proxy-Patch, Routes-Seed aber weiterhin offen.
  Browser-LKW fahren weiterhin Luftlinie × 1.3 (Fallback).
  Tatsächlich: `engine.loadContent` liest `window.LOGISTIK_SEEDS.routesOsm`
  falls gesetzt → wenn Atlas den Wrapper erweitert, greift Polyline-Routing
  automatisch.
- Glossar-API-Pattern (Atlas koordiniert mit Glossar-Instanz)

### Browser-Test jetzt
- [http://localhost/geograsim/App/logistik?level=2](http://localhost/geograsim/App/logistik?level=2):
  Multi-Tour, Minigame 🎮 mit Countdown + Sounds + Mover
- [http://localhost/geograsim/App/logistik?level=3](http://localhost/geograsim/App/logistik?level=3):
  + Bahn + Häfen + Events + Schiffsankünfte + schnellerer Mover

---

## 2026-04-22 ~14:00 — Phase 7b ausgeliefert (nach Atlas-12:00-Mail)

### Gebaut
- **`App/sims/logistik/admin-fields.json`** — alle 19 `params`-Keys aus balance-matrix.md §5 mit `label`, `labelEasy`, `type`, `unit`, `min/max/step`, `options`, `default`, `help`. Gruppiert in 7 Abschnitten (Wirtschaft, Auftragslage, Fahrzeugpool, Didaktik & Hilfen, Ereignisse, Verkehrstraeger, Sonstiges). Bonus: `availableVehicleTypes` als `multienum`, `levelNameEasy` als `text` für Leichte Sprache.

- **`App/php/api/logistik-sessions.php`** — POST start/end, GET status. Persistiert ohne neue Tabelle via `game_saves` mit Namespace-Keys `logistik:active-attempt` / `logistik:history` (Ring-Puffer letzte 20 Durchgänge). Security: Cookie-Session gegen `student_sessions` validiert.

- **`App/php/api/logistik-saves.php`** — GET/POST/DELETE. 5 Slots (1–5) via Keys `logistik:save:<slot>`. 500 KB-Limit. Baut auf generischer `game_saves`-Tabelle auf — keine Duplikat-Tabelle zu `saves.php`.

- **`App/php/api/logistik-analytics.php`** — POST in `lg_contracts_log` (Atlas-Schema mit intermodal + route_distance_km + route_mode). GET liefert 3 Scopes: `me` (eigene letzten 50), `class` (Lehrer-only, pro Session), `level` (Lehrer-only, pro Level-Aggregation).

- **Engine-Hook**: `game.pendingAnalytics` als neue Queue. `_pushAnalyticsEntry()` wird in `_completeContract` (Single) und `_completeContractMulti` (Tour) aufgerufen. Engine selbst macht keinen Netz-Call — bleibt in Node/Browser gleich testbar. UI drainiert die Queue alle 2s und POSTet (best-effort).

- **game.html-Integration** — `LG_API`-Helper: fire-and-forget mit `credentials: same-origin`, 401 → bis Session-Ende offline. Auf Game-Init: `startAttempt(levelNum)`. Auf LEVEL_SUCCESS/FAIL: `endAttempt(success, stats)` + Last-Drain der Analytics. Save/Load-Buttons jetzt Server-first mit localStorage-Fallback + Leichte-Sprache-Toasts.

### Security-Minimum (wie Atlas vorgegeben)
- Jeder POST validiert Session-ID aus `ggs_session`-Cookie gegen `student_sessions`-Tabelle (nicht nur Cookie-Präsenz)
- Keine Fremd-Session-IDs schreibbar — `session_id` kommt IMMER aus Server-Cookie, nie aus Request-Body
- DELETE/GET auf Saves nur für eigene Session
- `class`/`level`-Analytics-Scopes nur mit `requireTeacher()`

### Offene Punkte (an Atlas-Review)
- `logistik-saves.php` legt nicht in einer eigenen Tabelle ab, sondern namespaced im vorhandenen `game_saves`. Falls du stattdessen eine `lg_saves`-Tabelle willst, Migration + Refactor-Patch.
- `logistik-sessions.php` speichert Aktiv-Attempt ebenfalls in `game_saves`. Einfach, kein Schema. Falls du reportingfähige `lg_attempts`-Tabelle willst, sag Bescheid.
- Ich habe **keinen Browser-Test** gemacht — es hängt daran, ob die bestehende `ggs_session`-Cookie auch unter `/App/logistik?level=1` mitkommt (sollte dank `BASE_PATH`-Konfiguration, aber ungeprüft).

### PHP lint
```
/c/xampp/php/php.exe -l App/php/api/logistik-sessions.php → OK
/c/xampp/php/php.exe -l App/php/api/logistik-saves.php    → OK
/c/xampp/php/php.exe -l App/php/api/logistik-analytics.php → OK
```

### Was Atlas jetzt tun kann
1. **Admin-UI** gegen `admin-fields.json` bauen — alle Feld-Metadaten stehen
2. **Review** der 3 PHP-Endpunkte, besonders: ist die `game_saves`-Kooptierung für Sessions/Saves ok oder dedizierte Tabelle?
3. **Meister** anpingen, damit das `lg_contracts_log`-Schema auf Produktion ist
4. **Glossar-API** — sobald die 13 Begriffe da sind, einbauen (Inline-Fallback in game.html funktioniert bis dahin)

---

## 2026-04-23 — Heute gebaut (lange Session)

### Neu-Mails von Atlas (beide gelesen)
- **2026-04-22 12:30 ElevenLabs-Sound-Pipeline**: zentrales Script
  `App/scripts/generate-sounds.py`, API-Key liegt, Pattern
  `App/sims/logistik/scripts/sounds-list.json`. Vorgeschlagene
  Liste mit 22 Sounds (~3-5 €). **Noch nicht ausgeloest** — wartet
  auf Thomas-Kommando, dann einmaliger Aufruf.
- **2026-04-23 11:00 Integrations-Auftrag**: Logistik ist Referenz.
  Drei Punkte: (a) mode-check im Wrapper (logistik.php), (b)
  Assessment-Calls bei level-start/running/completed, (c) Phase-7b
  — Atlas listete das noch als „offen", das ist aber seit
  2026-04-22 14:00 geliefert (siehe zentrale/2026-04-22-1400-logistik-phase7b-fertig.md).
  Atlas hat noch nicht reviewt.

### Rangier-Minispiel — Phase 3 komplett neu gebaut (Thomas-Wunsch)
- **Top-Down-Ansicht** (Lok + Waggons rotieren mit Schiene)
- **Canvas 1120×400**, aspect 14/5
- **17 Segmente**: 4 Hauptgleis + 3 Sidings + 3 Top-Branches + 3 Bottom-Branches + 4 Sammelgleis (inkl. 20px Stub samm_R fuer W6)
- **6 Weichen** W1-W6, Tasten 1-6 oder Klick
- **Endpunkt-basierte Verbindungen** (SG_LINKS + SG_SWITCHES), `sgConnected(segId, endpoint)`
- **Direction-Tracking** `{segId, s, dir}` mit korrektem `sgTransitionOut`-Helper: Lok-Heading via Dot-Produkt projiziert. Buggy alter Code flippte dir bei geraden Uebergaengen → Zug faltete sich. Fix verifiziert.
- **Consist-Modell** mit Items-Array, auto-Kupplung am Wagen-Ende (nicht an Lok-Nase → Lok-Front blockiert mit Warnung)
- **Entkupplung** per ✂-Klick in Luecken (nur im Stillstand, senkrecht vom Gleis gezeichnet)
- **Waggon-Labels** `#1 #2 #3 #4` statt Stadt-Emojis (Thomas-Wunsch — klarer)
- **Switch-Warning**: roter pulsierender Ring + Web-Audio-Warnton (2× 880Hz Square) wenn: Lok faehrt gegen Sackgasse / Lok-Front rammt geparkte Wagen / Weiche umschalten wenn Zug drauf
- **Zeit-Bonus**: < 4 Minuten → `timeBonus: true` im `applyMinigameResult`-Result

### L1-Innen-Progression (Thomas-Wunsch: steigernd)
- `game.progression = { round, trucksBought }` in `createGame`
- `_effectiveMaxContracts(game)`: L1 waechst dynamisch 1 → 2 (Runde 2-3) → 3 (Runde 4+)
- **Pool mit 5 Staedten** statt Wien↔Salzburg-Pendel: Wien/Salzburg/Muenchen/Berlin/Hamburg mit Reward-Multiplikatoren 0.8–1.7
- **Staffel-Freischaltung**: Runde 1-2 nur `mult ≤ 1.0`, Runde 3-4 ≤ 1.4, ab Runde 5 voll
- **`awayBonus: true`-Flag** auf Auftraegen mit Leerfahrt zum Pickup — UI-Badge noch zu bauen
- **Auto-Buy TRUCK_SMALL**: ab Balance ≥ 15.000 € spawnt automatisch neuer Kleiner LKW (Kosten 5.000 €), max 3 Fahrzeuge
- Runden-Increment in BEIDEN `_completeContract*`-Pfaden

### Balance-Regel R-1 (Startbudget-Runway)
- Formal: `startBudget ≥ 1.5 × (rental×vc + idle×vc×12)`
- **L3 verletzt R-1** (2.500 € startBudget vs. 4.650 € benoetigt)
- L1/L2 passieren
- Python-verifiziert: L3-Runway 0.81–0.89 Tage — Tag 1 endet sicher im Minus
- Doku: `App/sims/logistik/level-progression.md` (L1-Design §1 + L3-Analyse + R-1 §2)

### 3 neue zentrale-Mails an Atlas (alle offen)
1. **2026-04-22 14:30** ElevenLabs-Frage — von Atlas beantwortet (Pipeline-Doku, s.o.)
2. **2026-04-23 17:00** Warnton-Asset (Heli-Ton wiederverwenden?) + Konzept „Minispiel-an-Fahrt-Koppeln" (bonus auf Lade-/Entlade-Zeit)
3. **2026-04-23 17:30** L3-Balance kaputt + Regel R-1 zur Pruefung + 3 Fix-Optionen (Empfehlung: startBudget 2.500 → 5.000)

### Was auf Atlas wartet
1. Admin-UI gegen `admin-fields.json` (von Phase 7b)
2. Review der 3 PHP-Endpunkte (game_saves-Kooptierung ok?)
3. Meister-Ping fuers `lg_contracts_log`-Schema auf Produktion
4. Entscheidung L3-Balance (A/B/C aus 17:30-Mail)
5. Daumen hoch fuer R-1 als Invariante
6. Heli-Warnton-Pfad freigeben
7. Buy-in fuer Minispiel-an-Fahrt-Koppeln-Architektur

### Was auf Thomas wartet (optional, kein Blocker)
- Sounds generieren: `python App/scripts/generate-sounds.py logistik` nachdem
  `sounds-list.json` unter `App/sims/logistik/scripts/` angelegt ist
  (kostet 3-5 € ElevenLabs-Kontingent)
- Entscheidung ob L1-UI-Badges fuer Runde + awayBonus dazu sollen

### Offene Arbeit bei mir (wenn Atlas freigibt)
- Integrations-Auftrag (mail 23.04 11:00): mode-check im logistik.php + Assessment-Calls
- `sounds-list.json` anlegen + Pipeline-Run (auf Thomas-Kommando)
- End-Screen auf `.ggs-endscreen`-Komponente umstellen (sobald Atlas die Inbox-Meldung schickt)

---

## 2026-04-23 — Nachtrag (Integrations-Auftrag + Polish)

### Integrations-Vertrag Kap. 6 umgesetzt
- **logistik.php**: Session-Cookie → `student_sessions` → `class_modules` +
  `student_modules` + `students.easy_language`. Setzt
  `LOGISTIK_SESSION_MODE` (`free` | `teacher_started` | `locked`),
  `LOGISTIK_FORCED_LEVEL`, `LOGISTIK_EASY`, `STUDENT_EASY`.
  Locked-Fall: HTTP 403 + minimale Sperrseite mit Cockpit-Link.
  PHP-Lint: ok.
- **game.html** respektiert `LOGISTIK_FORCED_LEVEL` bei
  `mode === 'teacher_started'` (URL ?level wird ignoriert).
- **Assessment-Calls** via `LG_API.postAssessment(phase, payload)`:
  - `started` direkt nach Engine-Init
  - `running` alle 30 s Heartbeat aus RAF-Loop (mit balance, completed,
    active, ratios, round)
  - `completed` bei `LEVEL_SUCCESS`/`LEVEL_FAILED` (mit finalBalance,
    minTarget, success, kennzahlen)
  - Alle fire-and-forget via bestehende LG_API-Infrastruktur (401 ->
    session-disabled)

### L1-Progression-UI-Polish
- **Away-Bonus-Badge** auf Auftragskarten: `↗ woanders` in Beige, wenn
  `c.awayBonus === true` (= Origin nicht an Vehicle-Location)
- **Runden-Anzeige** im Header: Level-Badge zeigt jetzt `Level 1 · Runde 3`
  auf L1 (andere Level unveraendert)

### Regel R-1 engine-seitig als Soft-Check
- In `loadContent`: `_validateRuleR1(cfg)` loggt `console.warn` mit
  Formel + benoetigtem Betrag, wenn Level die Regel verletzt. L3
  triggert den Warn aktuell (2.500 € < 4.650 €). Nicht blockierend —
  Lehrkraft kann bewusst ueberschreiben.

### Manueller Fahrzeugkauf ab L2
- **Engine**: `LogistikEngine.buyVehicle(game, mode)` +
  `LogistikEngine.VEHICLE_PRICES` (TRUCK_SMALL 5k, TRUCK_LARGE 15k,
  TRAIN 40k). Prueft `availableVehicleTypes`, Balance, spawnt Fahrzeug,
  sendet `VEHICLE_PURCHASED`-Notification, incrementiert
  `progression.trucksBought`.
- **UI**: Button „🛒 Neues Fahrzeug kaufen" oben in der Fahrzeugliste,
  sichtbar nur wenn `maxActiveContracts > 1` (= L2+). Klick oeffnet
  Dialog-Overlay mit 3 Typen: Icon, Name, Kurz-Hint, €/km+€/h,
  Preis. Nicht-leistbare Fahrzeuge grau + Preis rot. Erfolg →
  Toast + Re-Render.

### Testbar jetzt
- [http://localhost/geograsim/App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1) — L1-Progression, Runden-Badge sichtbar, Auto-Buy bei 15k, Away-Bonus-Auftraege ab Runde 2 markiert
- [http://localhost/geograsim/App/logistik?level=2](http://localhost/geograsim/App/logistik?level=2) — „🛒 Neues Fahrzeug kaufen"-Button in der Fahrzeugliste
- [http://localhost/geograsim/App/logistik?level=3](http://localhost/geograsim/App/logistik?level=3) — R-1-Warn in der Browser-Konsole

### Was jetzt auf Atlas/Thomas wartet
- Atlas-Review aller 3 offenen Mails in zentrale (Phase-7b-Fertig,
  17:00 Warnton+Trip-Koppeln, 17:30 L3-Balance+R-1)
- Atlas-Meldung wegen `.ggs-endscreen`-Komponente (bis dahin bleibt
  unser Endscreen-Overlay)
- Thomas: ElevenLabs (morgen) und Sounds

### Deploy-bereit (auf Thomas-Kommando)
Alle Aenderungen lokal fertig. Kein DB-Migration noetig (alles via
bestehende `assessments`-Tabelle + bestehende `game_saves`-Keys + Engine-interne Progression).

---

## 2026-04-24 ~11:00 — Deploy von Atlas durch (Option B)

Atlas hat den Deploy selbst koordiniert — antwortete auf meine
Anfrage vom Morgen (`zentrale/2026-04-24-0900-logistik-deploy-anfrage.md`).

**Auf Prod live (https://geograsim.at/):**
- ✅ `pages/logistik.php` — mode-check + forcedLevel + easy + Sperrseite
- ✅ `php/api/logistik-sessions.php`, `logistik-saves.php`, `logistik-analytics.php`
- ✅ `sims/logistik/game.html` (~149 KB, aktueller Stand)
- ✅ DB: `lg_contracts_log` existiert und leer, bereit
- ✅ DB: `students.easy_language` additiv ergänzt (Default 0)
- ✅ Module-IDs-Migration sim-XX → klima/heli/fluss/logistik
- ✅ HTTP-200 auf `/logistik` und `/modul-logistik`

**Reviews geschlossen:**
- Phase 7b Fertig — gelesen, kein Review noetig
- Warnton + Trip-Integration — kommt als Phase 8e separat
- L3-Balance + R-1 — **Atlas delegiert an mich**: `game_levels.params`-
  Update mache ich selbst in einem Sweep, kein Atlas-Eingriff noetig

**Noch bei mir offen (nicht blockierend):**
- L3 startBudget 2.500 → 5.000 in `game_levels.params` via UPDATE-SQL
  (Empfehlung A aus meiner 23.04 17:30 Mail) — wartet auf Thomas-OK
- Sounds — Thomas kuemmert sich um ElevenLabs
- End-Screen-Umstellung auf `.ggs-endscreen` — wartet auf Atlas-Komponenten-Meldung

---

## 2026-04-24 ~13:00 — Alle delegierten Schritte umgesetzt

### L3-Balance-Fix
- **SQL-Migration**: `App/Don_t_Deploy/2026-04-24-logistik-l3-balance-fix.sql`
  setzt L3 `params.startBudget` auf 5.000 (war 2.500).
- **Lokal ausgerollt** auf XAMPP-MariaDB. Verifikation OK.
- Wartet auf naechsten Deploy → Atlas (siehe Mail
  `zentrale/2026-04-24-1300-logistik-l3-migration-und-balance-korridor.md`).

### Balance-Korridor-Doku + Mechanik
- Neue Doku: `App/sims/logistik/balance-corridor.md`
- Drei Regeln formal:
  - **R-1 Runway**: `startBudget ≥ 1.5 × Tagesfixkosten` — Engine-Warn.
  - **R-2 Winnable**: per Level min. 1 Strategie schafft Median-success
    ueber 5 Seeds.
  - **R-3 Loseable**: noop schlaegt 100% fehl, zusaetzlich strategie-
    spezifische Anforderungen pro Level.
- Neuer Button `Balance-Korridor-Check (R-1/R-2/R-3)` in
  `headless-runner.html`: faehrt 45 Durchlaeufe (3 Lvl × 3 Strategien
  × 5 Seeds) und liefert PASS/FAIL pro Regel + Level. Auch greedy als
  Proxy fuer optimal verwendet (Stub fehlt noch).
- Aktuelle Verifikation R-1 mathematisch: L1/L2/L3 alle PASS nach Fix.

### Bekannte Luecke (nicht-blockierend)
- `optimal`-Strategie in `headless-runner.js` ist noch Stub (wirft).
  Damit ist L3-Winnability nicht hart-verifiziert. Folgearbeit, sobald
  nichts dringender ist.

### Atlas-Mail raus
`zentrale/2026-04-24-1300-logistik-l3-migration-und-balance-korridor.md`
mit SQL-Migration zum naechsten Deploy + Korridor-Doku zur Kenntnis.

---

## 2026-04-24 ~14:00 — optimal-Strategie implementiert

Die letzte offene Luecke im Balance-Korridor geschlossen.

### Was gebaut
`headless-runner.js` — `optimal` ist nicht mehr ein Stub, sondern eine
**Best-Net-Heuristik**:
- Pro Tick alle (contract, vehicle)-Paarungen bewerten:
  `net = reward × 1.15 − fare − erwartete Verspaetungsstrafe`
  mit `fare = pickup-km × €/km + pickup-h × €/h + delivery-km × €/km + delivery-h × €/h`
  und `expectedPenalty = rewardBase × latePenaltyRate × max(0, totalH − slackH)`
- Kapazitaets-Constraint hart (Skip wenn veh.capacity < c.containers)
- Paarungen nach Netto sortiert, oben-nach-unten feuern, pro Iteration
  jedes Vehicle/Contract max. 1×
- Unprofitable Paarungen (net ≤ 0) werden ausgelassen

~120 Zeilen, UMD-konform (laeuft in Browser + Node). Intermodal noch
ausgeklammert (wie greedy).

### Korridor-Check jetzt vollstaendig
- `headless-runner.html` → „Balance-Korridor-Check" faehrt jetzt **60
  Durchlaeufe** (3 Lvl × 4 Strategien × 5 Seeds)
- R-2 ueber `optimal` hart verifizierbar (nicht mehr nur greedy-Proxy)
- R-3 strenger: zusaetzlich zur noop-Regel wird fuer L2 naive und L3
  greedy auf Median-Fail geprueft (Level muss schwerer werden, nicht
  nur durch Untaetigkeit verlierbar)

### Docs aktualisiert
- `balance-corridor.md` §5 + §7: Status auf vollstaendig, Luecke
  geschlossen
- `headless-runner.js` Header-Kommentar: Strategie-Beschreibungen
  korrekt

### Test-URL
`http://localhost/geograsim/App/sims/logistik/headless-runner.html`
→ 3. Button „Balance-Korridor-Check (R-1/R-2/R-3)" druecken → 60 Runs,
Ergebnis-Tabelle mit PASS/FAIL pro Regel + Level + Detail-Zahlen.

### Was jetzt noch offen bleibt
- L3-SQL-Migration wartet auf naechsten Deploy (an Atlas via Mail 13:00)
- ElevenLabs-Sounds (Thomas morgen)
- End-Screen-Umstellung auf `.ggs-endscreen` (wartet auf Atlas)
- Phase 8e Warnton + Trip-Integration (wartet auf Atlas-Meldung)

---

## 2026-04-24 ~16:00 — Phase 8e: L1-Stufen-Progression mit Accept/Decline

Auf Thomas-Wunsch: L1 in 6 Stufen geteilt mit jeweils einer neuen
Entscheidungsdimension pro Stufe. Konzept in diesem Status-Update
knapp, ausfuehrlich in `level-progression.md` nachtragbar.

### Die 6 Stufen (L1-intern, reine Engine+UI-Mechanik, kein DB-Change)

| Stufe | ab Runde | Neu | Mechanik |
|-------|---------|-----|----------|
| 1 Fahren lernen      | 1  | START druecken | 1 Auftrag direkt OPEN, kein Accept noetig |
| 2 Rueckweg waehlen   | 2  | Annehmen/Ablehnen | offers-buffer=1, Standard-Varianten im Pool |
| 3 Zeitdruck          | 4  | Express-Varianten | 30 % Chance auf Eilauftrag (halbe Frist, +50 % Reward) |
| 4 Container-Grenze   | 6  | Multi-Container + TRUCK_LARGE-Unlock | 25 % Chance auf 2-Container, Kauf-Dialog zeigt TRUCK_LARGE |
| 5 Parallel disponieren | 8 | 2 OPEN gleichzeitig + offers-buffer=2 | volle Pool-Breite, Distanzen bis 1.5× Mult |
| 6 Soft-Ramp L2       | 11 | Events 0 → 0.3, Fernstrecken | eventProbabilityMultiplier=0.3, poolMaxMult=1.7 |

### Neue Engine-Funktionen
- `CONTRACT_STATE.OFFERED` + `EXPIRED`
- `LogistikEngine.acceptOffer(game, contractId)` — setzt OFFERED→OPEN, prueft max-OPEN-Slots
- `LogistikEngine.declineOffer(game, contractId)` — entfernt OFFERED
- `_l1Stufe(game)` / `_l1StufeCfg(game)` — Stufen-Aufloesung via round
- `_totalContractSlots(game)` — max-OPEN + offers-buffer
- `_maybeRefillOffers(game)` — fuellt total-slots mit OFFERED-Angeboten
- `_expireOffers(game)` — entfernt abgelaufene OFFERED nach Sim-Zeit
- `_maybeGenerateEvents` + `buyVehicle` beachten L1-Stufen-Overrides
- Offers expirieren nach **30 Sim-Minuten** stumm
- Tick ruft `_expireOffers` + `_maybeRefillOffers` jeden Tick

### UI-Neu
- OFFERED-Karten: eigenes Beige-Farbthema, Countdown `⏳ Verfällt in MM:SS`
- Buttons `✓ Annehmen` / `✕ Ablehnen` pro Angebot
- Express-Badge `⚡ Eilauftrag` + Multi-Container-Badge `×2`
- Header-Badge: `Level 1 · Stufe 3 Zeitdruck · R 5`
- Kauf-Button erscheint auf L1 ab Stufe 4 (Container-Grenze)
- Kauf-Dialog zeigt TRUCK_LARGE auf L1 Stufe 4+
- RAF-Loop: Sekunden-Trigger fuer Countdown-Update wenn Offers aktiv

### Headless-Runner angepasst
- `naive`/`greedy`/`optimal` akzeptieren OFFERED automatisch (Stufe 1 hatte keine, jetzt ab Stufe 2)
- `noop` laesst Offers verfallen → L1 fail wie erwartet

### Testbar
- [http://localhost/geograsim/App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1) — Runde 1: ein Auftrag. Nach Lieferung: 2 Angebote, echte Wahl
- Balance-Korridor-Check (`headless-runner.html`): laeuft jetzt ueber die neue L1-Mechanik

### Deploy-Relevant
- Keine DB-Aenderung → keine Migration noetig
- Reiner Code-Deploy (engine.js + game.html)

---

## 2026-04-24 ~17:00 — Phase 8e Rückbau (L1 zu komplex, zurück zu simpel)

Thomas-Feedback: „das mit dem Stress, Eilauftrag, geht viel zu schnell,
keine Reaktionszeit. In Level 1 gibt es keinen Eilauftrag. Aufträge
kommen und gehen, sind vielleicht irgendwann auch weg ... aber sonst
zu kompliziert." → simpler machen.

### Was rausgenommen
- **CONTRACT_STATE.OFFERED** aus dem L1-Flow (Enum bleibt im Code, aber
  Generator setzt es nicht mehr). Aufträge erscheinen direkt als OPEN.
- **Accept/Decline-Buttons** auf den Auftragskarten
- **Countdown** „⏳ Verfällt in MM:SS"
- **Express-Badge** `⚡ Eilauftrag` und **Multi-Container-Badge** `×2`
- **RAF-Sekunden-Trigger** (war nur für Countdown)
- **TRUCK_LARGE-Unlock ab L1 Stufe 4** in `buyVehicle` + UI-Kauf-Dialog
- **Kauf-Button auf L1** generell (Auto-Buy reicht)

### Was bleibt (einfaches Modell)
- **5 Stufen** (statt 6), jeweils `maxOpen` + `poolMaxMult` + `eventMult`:
  - Stufe 1 Erster Auftrag (R1): 1 OPEN
  - Stufe 2 Mehrere zur Wahl (R2): 2 OPEN, gleicher Pool
  - Stufe 3 Weitere Strecken (R4): 2 OPEN, Pool bis Mult 1.3 (München)
  - Stufe 4 Mehr Fahrzeuge (R7): 3 OPEN, Pool bis Mult 1.5
  - Stufe 5 Soft-Ramp → L2 (R11): 3 OPEN, Pool 1.7 + Events 0→0.3
- **„Kommen und gehen"**: `_expireOffers` (nur auf L1 aktiv) entfernt
  OPEN-Auftraege die ihre eigene `dueTime` ueberschritten haben ohne
  Fahrzeug-Zuweisung. Silent-Remove + kurze Notification. Keine
  eigene Ablaufzeit, keine gelben Ringe, kein UI-Alarm — die Frist ist
  die natuerliche Grenze.
- **Auto-Buy** TRUCK_SMALL bei Balance ≥ 15.000 €: unverändert
- **Pool-Staffelung** nach Distanz (Stufen-poolMaxMult): unverändert
- **Away-Bonus-Badge** auf Auftragskarten: bleibt (ist subtil, nicht stressig)
- **Runden-/Stufen-Anzeige** im Header: aktualisiert auf neue Stufen-Namen

### Engine-Seite
- `L1_STUFEN[]` hat jetzt nur `{n, name, minRound, maxOpen, poolMaxMult, eventMult}`
- `_effectiveMaxContracts(game)` nimmt `stufe.maxOpen` direkt
- `_totalContractSlots = _effectiveMaxContracts` (kein Buffer-Konzept mehr)
- `_generateL1ContractFromPool`: immer OPEN, 1 Container, nicht Express
- `_expireOffers` → silent-removes OPEN mit abgelaufener dueTime (nur L1)

### Headless-Runner
- `acceptOffer`-Aufrufe in naive/greedy/optimal bleiben stehen
  (try/catch — no-op wenn keine OFFERED-Angebote da sind)
- L1 sollte jetzt wieder mit noop=fail, naive=success laufen

### Testbar
[http://localhost/geograsim/App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1)
— Runde 1: ein Auftrag (wie altbewährt). Nach Lieferung: zwei
Aufträge parallel, ohne Accept-Klick, jeder mit eigener dueTime. Wer
ignoriert wird, verschwindet irgendwann still. Kein Stress, keine
Eilaufträge, keine Countdown-Alarme.

### Nicht-Deploy-blockierend
Keine DB-Änderung, reiner Code-Deploy.

---

## 2026-04-24 ~18:00 — Phase 8e Ausbau: Variety, History, Verfahren-Event

Thomas-Feedback: „Ab der zweiten Fahrt sollte es 3-4 Optionen geben.
Manchmal attraktiver mit längerer Anfahrt, manchmal finanziell gut
aber zeitlich knapp — muss an den Zahlen erkennbar sein. Fahrt-Protokoll
als Pill." Plus Zusatzaufgabe: „Fahrer:in hat sich verfahren"-Event.

### Mehr Optionen ab Runde 2
`L1_STUFEN.maxOpen` erhoeht:
- Stufe 1 (R1): 1 Auftrag
- Stufe 2 (R2+): **3** Auftraege parallel (war 2)
- Stufe 3+ (R4+): **4** Auftraege parallel
- `poolMaxMult` auch schneller erweitert: Stufe 2 schon auf 1.3 (München)

### Profile-Variance im Generator
Jedes Angebot bekommt ein Profil gewuerfelt (Stufe 1 immer `standard`):
| Profil | Gewicht | Reward-Mult | Frist-Mult | Pool-Filter |
|--------|---------|-------------|------------|-------------|
| standard | 50 % | 1.0 | 1.0 | — |
| **tight** (Frist knapp) | 20 % | 1.3 | 0.6 | — |
| **far** (Weite Fahrt) | 15 % | 1.0 | 1.0 | mult ≥ 1.5 |
| **easy** (Kurz) | 15 % | 0.85 | 1.3 | mult ≤ 1.0 |

Profil-Name wird auf `contract.profile` gespeichert, UI-Badge rendert:
- ⚡ knappe Frist (orange)
- 🌍 weite Fahrt (blau)
- 🌿 kurz (grün)

### Zeit-Reserve-Berechnung sichtbar
Beim Klick auf Fahrzeug (Lademodus) rechnet jede Auftragskarte jetzt:
- `Fahrkosten ≈ X €` + `Marge ± Y €` (wie bisher)
- **NEU:** `⏰ Z.Z h Reserve` in Grün/Orange/Rot:
  - ≥ 3 h grün (entspannt)
  - 1–3 h orange (knapp)
  - 0–1 h rot (kritisch)
  - < 0 h rot „Frist NICHT zu schaffen"

Dadurch erkennt der Spieler an den Zahlen: `⚡ knappe Frist`-Auftraege
mit hohem Reward sind nur machbar wenn die Zeit-Reserve es zulaesst.

### Fahrt-Historie als Pills
Neuer UI-Strip zwischen Status-Leiste und Didaktikfenster:
- `📜 Historie: Fahrt 1 +523 € | Fahrt 2 −120 € | Fahrt 3 +890 €`
- Grüne/rote Pills je nach Netto-Vorzeichen
- Klick auf Pill → Detail-Modal mit Strecke, Container, Dauer, Verspätung, Netto

Strip erscheint nur wenn ≥ 1 Lieferung abgeschlossen. Horizontal scrollbar
bei vielen Pills.

### Zusatzaufgabe: Verfahren-Ereignis
- **Engine-Trigger**: Mit 6 % / Sim-Stunde pro Fahrzeug im `*_drive` ab
  L1 Stufe 2 wuerfelt ein Verfahren-Event. Bei Treffer:
  - `vehicle.verfahrenPending = true`
  - `vehicle.verfahrenTargetLocationId` = aktueller Trip-Zielort
  - `vehicle.verfahrenDriverLabel` = „Fahrerin Anna" oder „Fahrer Tom"
    (alternierend ueber `game.progression.verfahrenCount`)
  - Fahrzeug pausiert in `_updateVehicles` (keine Bewegung, keine Zeit-Verbrauch)
  - Notification `DRIVER_LOST` wird gefeuert
- **UI-Banner** am oberen Map-Rand:
  `🤷 Fahrerin Anna hat sich verfahren! Bring sie nach Salzburg — klick den Ort auf der Karte.`
- **Map-Klick-Handler** pro Stadt-Marker:
  - Wenn ein Fahrzeug `verfahrenPending` hat und der geklickte Ort das Ziel ist →
    `LogistikEngine.resolveDriverLost(game, vehicleId, locationId)` → Fahrt geht weiter
  - Falscher Ort → Toast-Hinweis, Event bleibt aktiv
- Didaktik: uebt Kartenorientierung; Zielstadt muss identifiziert werden

### Minigame-Buttons korrigiert
- 🎮 Einparken: jetzt auf **allen Levels** sichtbar (war fälschlich an
  `minigamesEnabled` gebunden, dadurch L1 leer)
- 🚆 Rangieren: nur sichtbar wenn TRAIN im Fuhrpark (L1 hat keinen → Button aus)

### Testbar
[http://localhost/geograsim/App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1):
- Runde 1: 1 Auftrag (Tutorial)
- Runde 2+: 3-4 Optionen mit unterschiedlichen Profilen, durchgemischt
- Zeit-Reserve erscheint beim Klick auf Fahrzeug
- Trip-Pills unten nach jeder Lieferung
- Gelegentlich Verfahren-Event → Stadt auf Karte zeigen
- 🎮 LKW-Einparken-Button sichtbar

### Reiner Code-Deploy, keine DB-Aenderung

---

## 2026-04-24 ~19:00 — End-Screen transparent + L1 längere Spielzeit + Favicon

### End-Screen-Zahlen klarer
Alter End-Screen zeigte nur „Ø Netto/Auftrag" und „Endbilanz". Das war
irreführend, weil in Multi-Contract-Touren Fahrkosten im Tick
abgezogen werden, aber NICHT in `contract.finalBalance` — Summe der
avgNet × Anzahl stimmte nicht mit Endbilanz überein. Thomas sah:
3 × 1247 = 3741 vs. Endbilanz +2547, konnte es nicht nachvollziehen.

Neu im End-Screen — komplette Konto-Rechnung sichtbar:
- Startbudget
- Erlöse (Basis + Bonus)  +X €
- Strafen                 −X €
- Fahrkosten              −X €
- Laufende Kosten         −X €
- **Zuwachs (Konto)**     ±X €
- Min-Ziel

Jetzt kann der Spieler selbst ausrechnen, wo das Geld hin ist. Die
Zahlen-Quellen sind dieselben wie vorher (`game.analytics`), nur die
Zeilen transparenter gruppiert.

### L1 timeLimitHours 24 → 72
Thomas-Feedback: „das Spiel sollte deutlich länger gehen". L1-Tutorial
war bei 4×/8× nur 3-6 Echt-Minuten lang, fühlte sich gehetzt an.

SQL-Migration: [App/Don_t_Deploy/2026-04-24-logistik-l1-timelimit-72h.sql](../../Don_t_Deploy/2026-04-24-logistik-l1-timelimit-72h.sql)
setzt `game_levels.params.timeLimitHours` von 24 auf 72 (= 9 Echt-Min
bei 8×, 18 Echt-Min bei 4×). Andere Parameter unverändert: Min-Ziel
3.000 €, Events aus, BLINK_EXACT-Hilfe.

**Lokal ausgerollt** auf XAMPP. Aktueller DB-Stand:
```
L1: timeLimitHours=72 (war 24)
L2: timeLimitHours=48 (unverändert)
L3: timeLimitHours=72 (unverändert, startBudget=5000 seit letztem Fix)
```

### Favicon verdrahtet
`game.html` hatte keine `<link rel="icon">`-Tags → Browser-Tab zeigte
Generic-Icon. Jetzt analog zu heli/lehrplan/glossar:
- `game.html` referenziert `../../favicon.svg` + `../../favicon-96x96.png`
- `logistik.php`-Wrapper biegt beide Pfade auf absolute `BASE_PATH`-URLs
  um (im bestehenden `strtr`-Block)

### Offene Deploy-Items für Atlas
Sammel-Liste der lokalen DB-Änderungen, die noch auf Prod müssen:
1. `2026-04-24-logistik-l3-balance-fix.sql` — startBudget 2500 → 5000
2. `2026-04-24-logistik-l1-timelimit-72h.sql` — L1 timeLimit 24 → 72
3. Seed-Einträge `class_modules` für logistik='free' pro Klasse (lokal nur)

Diese drei Migrations sind reine `game_levels.params` / `class_modules`
Updates, keine Schema-Änderungen.

---

## 2026-04-24 ~19:30 — Auto-Boost Fahrtzeit

Thomas-Wunsch: „In Phasen, in denen nur Fahrtzeit erwartet wird, die
Timescale automatisch auf 8× springen und dann automatisch zurück."

### Mechanik
- **Gespeicherte Player-Wahl** (`playerSpeed`) vs. **aktuelle sim-Geschwindigkeit**
- Jeden Frame `updateAutoBoost()`:
  - Condition: `allBusy = alle Fahrzeuge haben tripPhase && keinen verfahrenPending`
  - `shouldBoost = allBusy && kein Verfahren-Event offen`
  - Wenn true + `!autoBoostActive` → setTimeScale(8), Flag setzen
  - Wenn false + `autoBoostActive` → setTimeScale(playerSpeed), Flag loeschen
- Klick auf einen Speed-Button: `playerSpeed` updaten, AutoBoost deaktivieren
  (manueller Override hat Vorrang)
- Pause (0×): Auto-Boost greift nicht

### UI
- Spieler-Button bleibt aktiv (visueller Anker)
- Neuer Badge `⚡ Auto 8×` rechts neben den Speed-Buttons pulsiert sanft
  gelb, wenn Auto-Boost aktiv
- Tooltip: „Alle Fahrzeuge unterwegs — Sim läuft automatisch auf 8×.
  Pressen einer Taste übernimmt wieder die Kontrolle."

### Auto-Release in diesen Fällen
- Ein Fahrzeug wird IDLE (Lieferung fertig) → Spieler könnte neuen Trip starten
- Verfahren-Event triggert → Spieler muss Karte klicken
- Spieler klickt selbst eine Speed-Taste

### Testbar
[http://localhost/geograsim/App/logistik?level=1](http://localhost/geograsim/App/logistik?level=1):
- Assign Auftrag zu Lok → Fahrt startet → sobald sie rollt: ⚡ Auto 8×
  Badge pulsiert gelb, Sim-Zeit rennt
- Lieferung fertig → Fahrzeug IDLE → Badge verschwindet, Zurück auf
  Spieler-Speed (normalerweise 1×)

---

## 2026-04-24 ~20:00 — Road-Dijkstra + Verfahren-Penalty + Auto-Boost-Override

### Straßennetz-Routing für LKWs
Thomas: „LKW fährt Luftlinie Salzburg→Berlin, unrealistisch."
- Neuer Seed `App/assets/data/lg-roadnet.json`: 11 Knoten (Stadt-Hubs) + 13 Edges mit echten Autobahn-Distanzen (A1/A7/A8/A9 etc)
- `logistik.php` injiziert Roadnet in `LOGISTIK_SEEDS.roadnet`
- `engine.js` `loadContent`: nimmt Roadnet in `worldState.roadnet` auf
- `calculateRoute` für TRUCK_SMALL/TRUCK_LARGE: erst Dijkstra auf Roadnet,
  Geometry = Polyline durch die Dijkstra-Knoten, Segments pro Kante mit
  `routeCode` (A8, A9 etc). Fallback routesOsm/Luftlinie für Locations
  ausserhalb des Roadnet-Graphs.
- Salzburg→Berlin fährt jetzt salzburg→muenchen→berlin (150+580=730km)
  statt Luftlinie (590km).

### Vertagte Follow-Up: Höhere „Auflösung" der Polylines
Thomas: aktuell sind Polyline-Segmente zwischen Stadt-Knoten gerade
Linien. Echte Autobahnen haben Kurven. Option C aus meinem Feedback:
hand-gepflegte Polylines pro Road-Kante in `lg-routes-osm.json`
(13 Paare × 5-10 Stützpunkte ≈ 1.5 h Arbeit). In die To-Do-Liste
aufgenommen, niedrige Prio.

### Verfahren-Ereignis: Zeit-Strafe + Capital-Only
Thomas-Wunsch: Stufe A (Zeit-Strafe statt sichtbarem Detour), nur
Hauptstädte zaehlen (sonst iPad-Fat-Finger-Problem).
- Engine `resolveDriverLost(game, vehicleId, clickedLocationId, locationType)`
  unterscheidet jetzt 3 Fälle:
  - `locationType !== 'CAPITAL'` → `ignored:true`, kein Strafe, UI zeigt
    „Nur Hauptstädte zählen"
  - Falsche Hauptstadt → `wrong:true, wastedHours:1.5`, akkumuliert in
    `vehicle.verfahrenPenaltyHours`, Event bleibt offen
  - Richtige Hauptstadt → löst Event auf, rechnet akkumulierte Strafe
    in Reduktion von `segmentProgress` um (max 90 % zurück) → Restfahrt
    dauert entsprechend laenger, late-fee-Risiko steigt
- UI-Marker-Klick reicht `loc.type` mit, Toasts differenziert nach Fall
- Konstante `VERFAHREN_PENALTY_H = 1.5` in engine.js

### TimeScale-Override bei Verfahren
Thomas: „Beim Verfahren TimeScale 1."
- `effectiveTimeScale()` mit Prioritaet: Pause → Verfahren 1× → AutoBoost 8× → playerSpeed
- Wenn Verfahren-Event triggert: egal ob vorher Player auf 4× oder AutoBoost 8× war, Sim springt sofort auf 1×
- Nach Resolution: zurueck zur richtigen Stufe
- AutoBoost-Badge wird waehrend Verfahren ausgeblendet

---

## 2026-04-24 ~20:30 — QoL: Auto-Select + Auto-Swap + Layer-Control raus

### Auto-Select beim Lademodus
Thomas: „Das oberste freie Fahrzeug ist automatisch schon markiert und
offen. Sobald es steht."
- In `renderVehicles()`: wenn kein `loadingVehicleId` → setze auf
  erstes IDLE-Fahrzeug ohne tripPhase. Auch beim Game-Start.
- Wenn das aktuell gewählte IDLE-Fahrzeug losfährt (tripPhase wird
  gesetzt) → automatisch auf nächstes IDLE umschalten
- Resultat: Spieler klickt nur noch Aufträge, das Fahrzeug ist
  vorausgewählt

### Auto-Swap im Laderaum
Thomas: „Wenn ein Auftrag bereits zugewiesen ist und ein anderer
angeklickt wird, der nicht mehr Platz hat, fliegt der Auftrag, der
bereits im LKW drin ist, wieder raus."
- In Auftragskarten-Klick-Handler:
  - Wenn Auftrag bereits im Fahrzeug geladen: **Toggle** → entladen
  - Wenn `loadContract` wegen vollem Laderaum failt: ältesten
    geladenen Auftrag `unloadContract`, dann neu versuchen
  - Toast-Message: „🔄 X geladen (Y ausgetauscht)" statt Fehler
- Spieler kann durch Aufträge schnell durchblättern

### Layer-Control-Widget entfernt
Thomas: „Die Auswahl zur Ansicht rechts oben (Hauptstädte, Städte,
Häfen, Terminals und Bahnnetz) kannst du entfernen. Wir zeigen einfach
immer alles an."
- `L.control.layers(...)` entfernt aus der Karten-Initialisierung
- Alle Layer werden unbedingt dem Map hinzugefügt (capitals, cities,
  ports, terminals); Bahnnetz nur wenn `railEnabled` im Level-Config
- Die Karte hat jetzt keine Widget rechts oben mehr

## 2026-04-24 21:30 — Bonus-Pills + Gratulations-Modal + Städte Tier 1 + Atlas-Mail

### Atlas-Mail raus: Info-Cards mit Bildern (Heli-Pattern)
- `_inbox/zentrale/2026-04-24-2100-logistik-infocards-heli-pattern.md`
- 5 konkrete Fragen: Heli-Card-Komponente, Bild-Pfad/Lizenz,
  Granularität (pro Auftrag vs. pro Zielort), AI-Pipeline,
  Design-System-Wiederverwendbarkeit
- Thomas-Wunsch: pro Start eine Info-Card mit Bild der Zielstadt
- Warte auf Atlas-Antwort; baue derweil andere Tasks

### Gratulations-Meldungsfenster
Thomas: „einen zweiten LKW bekommen. Dies wurde aber nicht angekündigt.
In einem solchen Fall bitte kurzes Meldungsfenster. Gratulation..."
- Neue Komponente `.lg-announce-overlay` + `.lg-announce-modal` in
  game.html (CSS + DOM)
- `announce(icon, title, text, sub)` — Scale-in-Animation, 4 Sek
  auto-close, Click schließt sofort
- VEHICLE_PURCHASED-Notification wird via `drainNotifications()` auf das
  Modal umgeleitet (statt Toast)
- `checkStufeChange()` vergleicht `_l1Stufe` mit `lastAnnouncedStufe`,
  feuert Modal bei Stufen-Wechsel mit Stufen-Parametern
  (maxOpen/poolMaxMult/eventMult)

### Bonus-Pills UI komplett
Thomas: „rechts oben Pills setzen, klickbar für Info, automatisch
benutzt. Stack-Limit gute Idee. Bonusskala passt."
- Container `#bonusPills` oben rechts über der Karte (absolute
  positioniert, `pointer-events: auto`)
- `renderBonusPills()` zeichnet die FIFO-Queue aus `game.pendingBonuses`
  (cap 5); jede Pill mit ⏱ Icon + Minutenwert + Quellen-Tooltip
- `showBonusPopover(idx)` — Click zeigt kurzes Erklär-Popover
  („Bonus aus Rangier-Minispiel — wird beim nächsten Laden angewendet")
- `flyBonusToVehicle(vehicle)` — Consumption-Animation: Pill fliegt
  zum Fahrzeug-Marker, skaliert auf 0, dann `pendingBonuses.shift()`
- Minispiel-Hook in `mgApplyResult`: bei Success `LogistikEngine.pushLoadBonus`
  mit 45/20/5 min je nach Zeit (<15s/<30s/<60s)
- Engine hat `_consumeNextLoadBonus(game, vehicle)` an 4
  Phase-Transitions (pickup/loading/unloading/dropoff)

### Layer-Fix: Ports + Terminals wirklich sichtbar
Fehler nach Layer-Control-Removal: `visibleLevel`-Filter im marker
forEach blockte Ports/Terminals (nur ab L3 sichtbar).
- Filter entfernt — alle Locations immer gerendert
- `bindPopup` zeigt jetzt Name + Typ-Label (Hauptstadt/Stadt/Hafen/
  Terminal) + `didacticInfo` als kleinen Absatz
- Popup-CSS `.lg-loc-popup` mit typeLabel-Subtitle

### Städte-Erweiterung Tier 1 (Hauptstädte)
Thomas: „Mache einen Entwurf und gib vor, wie viele Städte in Europa
relevant wären."
- `App/assets/data/lg-locations.json` von 13 auf **27 Einträge**
  erweitert (alle bestehenden Einträge behalten IDs/Koordinaten)
- **+14 Hauptstädte**: Rom, London, Brüssel, Amsterdam, Prag, Budapest,
  Athen, Stockholm, Oslo, Helsinki, Dublin, Lissabon, Bern
- Jeder Eintrag hat `didacticInfo` — 1 Satz mit logistik-relevantem
  Geo-Fakt (Verkehrsknoten, Hafen-Kapazität, EU-Status etc.)
- `regionId` konsistent gesetzt (benelux, scandinavia, iberia,
  western_europe, southern_europe, central_europe)
- **Offen**: Tier 2 (~50 Großstädte) + Tier 3 (~25 Häfen/Terminals)
  — Konzept steht, Umsetzung vertagt

### Auto-Timescale-Badge dynamisch
Thomas: „bei der Auto Timescale steht immer Auto 8× .. die Zahl sollte
sich aber ändern, wenn nicht reine Fahrt aktiv ist."
- Badge-Text wird jetzt pro Frame aus `effectiveTimeScale()` gesetzt
- `⚡ Auto 8×` bei reiner Fahrt, `⚡ Auto 1×` bei Verfahren-Event
  (wenn playerSpeed != 1)
- Badge hidden wenn Auto-Logik nicht override-t (playerSpeed==target)
- `updateAutoBoost()` resettet Badge auch bei non-RUNNING States
  (MINIGAME/PAUSED/LEVEL_SUCCESS), bisher blieb es stale sichtbar

### Offene Punkte
- Atlas-Antwort zu Info-Cards (5 Fragen vom 21:00)
- Städte Tier 2/3 (Konzept steht, ausbauen)
- Uhr mit Stunden/Minutenzeiger + Tag/Nacht in Karten-Ecke
- Pausepflicht LKW bei Langstrecken in höheren Levels
- Sounds via ElevenLabs (Thomas macht morgen)
- Polyline-Auflösung Option C (hand-gepflegt pro road-edge)

## 2026-04-26 Sammel-Eintrag — L1-Polish + L2-Neuanfang + Autobahn-Game

Zwei Tage intensives Arbeiten an L1 und L2. Vor /compact zusammengefasst.

### L1 — fertig poliert
- Auto-Save/Load entfernt (TODO als Memory). Buttons im Header weg.
- Strafen ausgeschaltet (`latePenaltyOverride: 0`); UI Strafen-Pill suppress
- Fahrer:innen-Namen statt "Kleiner LKW #1" (DRIVERS-Pool: Anna, Tom, Lisa, ...)
- "Vor-Marge" → "Gewinn ca.", "(geschätzt)" → "ca.", Fahrzeug-Card kompakter
- Lademodus-Banner zweizeilig: "Lademodus" / "Klicke auf Aufträge"
- "Bilanz" → "Umsatz" + "Gewinn"; Click-Explainer-Popovers an allen Status-Items
- Real-Laufzeit-Pill ⏱ X:SS min beim State-Badge
- Frist-Balken: ganzer Truck muss in grüner Zone, Fill rechts verankert
- Auftragsliste filtert nur OPEN+RESERVED, sortiert nach Container ASC + Frist ASC
- X-Button auf Cards zum Verwerfen, abgelaufene Frist ausgeblendet
- Train-Voice nur bei `autorent_*`-IDs, nicht bei manuellen Käufen

### L2 — kompletter Neuanfang nach Thomas-Konzept
Aktueller L2 ersetzt das alte komplexe L2 (das alte ist als Beta-4 archiviert in `Don_t_Deploy/2026-04-25-logistik-l2-archive-as-beta4.sql` — nicht in DB).

**Konfig**: 6 Tage (144h), Startbudget 8 000 €, 5 OPEN-Slots, 2 Start-Trucks, Tagesmiete 400 €/Truck, keine Strafen, Bankrott-Regel, Sterne-System.

**Fahrzeuge**:
- TRUCK_SMALL Cap 1 in Wien (Anna)
- TRUCK_MID Cap 2 in München (Tom) — neuer Mode mit costPerKm 1.6, costPerHour 30, speedKmh 65, rentMult 1.0
- Manueller Kauf via 🛒-Dialog: TRUCK_SMALL/MID/TRAIN_SMALL listed
- Train ab Tag 5 mietbar (96h), 5 000 € Anzahlung, Tagesmiete ×3 (= 1 200 €/Tag bei 400 base), Cap 6, Spawn Hamburg, max 1 Train pro Spiel
- Auto-Rent **deaktiviert** in L2 (nur L1)

**L2_OFFER_POOL** (Tier 1 + Tier 2):
- Tier 1: 24 Routen DACH/Brenner/Berlin (1- und 2-Container)
- Tier 2: 20 weitere Routen ab Runde 3 (Frankreich, Madrid, Hamburg-Rotterdam-Berlin-Kopenhagen-Warschau, Häfen)
- Refill: bis 5 OPEN-Slots gefüllt UND mindestens 2 OPEN-1-Container (separate Garantie-Schleife)
- L2_TRAIN_POOL: 11 Routen, davon 6 Hamburg-Hub-Routen für den Train-Spawn-Ort
- Train-Wave: jeder 3. Auftrag ab Train-Anmietung train-tauglich (4-6 Container, blaues "🚂 Zug-tauglich"-Badge), letzte 24 h keine Train-Aufträge mehr

**Roadnet erweitert**: 6 neue Hafen-Knoten + 8 Edges via OSRM (hamburg_hafen, rotterdam_hafen, triest_hafen, genua_hafen, goeteborg_hafen, gdansk_hafen).

**Hafen-Marker** als ⚓-Icons (PORT) und 📦 (TERMINAL), statt Kreise.

**Verfahren ab Runde 2 in L2**, gleiche Mechanik wie L1, Driver-Label aus v.driverLabel.

### Autobahn-Game (Phase 9, ab L2)
Externer Entwickler hat das Modul nach meiner Spec gebaut (`spec-autobahn-minigame.md`). Lieferung war 1:1 spec-konform.

**Files**:
- `App/sims/logistik/assets/vendor/three/three.min.js` (r128, 603 KB)
- `App/sims/logistik/assets/vendor/autobahn-game/autobahn-game.js`
- `App/sims/logistik/assets/vendor/autobahn-game/autobahn-game.test.html`

**Trigger** (`checkAutobahnGameTrigger` im RAF): nur L2, LKW in `*_drive`-Phase, < 8 km von einem der 6 NODES (bregenz, salzburg, innsbruck, muenchen, leipzig, verona) → Modal öffnet, pro vehicleId+routeId nur 1×.

**Result-Mapping**:
- success → +30 min Bonus-Pill, Voice "autobahn_success"
- late → +30 min auf `route.totalDurationMinutes`, Voice "autobahn_late"
- crash → Vehicle ZURÜCKGESETZT (tripPhase=null, IDLE, geladene Aufträge zurück OPEN, route entfernt) + 2 000 € Reparatur + Voice "autobahn_crash". Vehicle-Ausfall 24h via `repairUntil` gesetzt aber Engine wertet's noch nicht aus — TODO Phase 9b.

### Speed-Buttons
- Pause / Auto / **8× fix** (neu, für „Fahrzeug stehen geblieben"-Fall) / 🚀 16× Turbo (Dev)

### Audio-Pipeline
70+ Voice-Lines via OpenAI TTS (`nova`, `tts-1`), Generator: `scripts/generate-voice-lines.py`, JSON: `assets/audio/voice-lines.json`.
Neue Lines diese Session: `minigame_hint`, `all_negative_hint`, `train_rented`, `autobahn_success/late/crash`, generische `day_1` bis `day_5_end`, `halftime`, `last_day`.
Music-Ducking: `lgAmbientDuck/Unduck/Start` exportiert. Country-Playlist 15 Tracks (5 alte + 10 neue von Thomas).

### Splash-Bilder
- L1 + L2 Splash-Modal mit DALL-E-Bildern (1000x571 px, ~800 KB)
- 55 Stadt-Bilder (Tier 1+2+3) generiert + downscaled

### SQL-Migrationen liegen in `Don_t_Deploy/`
- 2026-04-24-logistik-l1-timelimit-72h.sql
- 2026-04-24-logistik-l1-rental-500.sql
- 2026-04-24-logistik-l1-neues-ziel.sql
- 2026-04-24-logistik-l3-balance-fix.sql
- 2026-04-25-logistik-l1-keine-strafe.sql
- 2026-04-25-logistik-l2-neuanfang.sql
- 2026-04-25-logistik-l2-archive-as-beta4.sql (Doku, kein INSERT)
- 2026-04-25-logistik-l2-maxcontracts-5.sql (in DB schon drin)

### Was als nächstes
- Train-Wave testen (1-sinnvoll-2-sinnlos in der Praxis)
- Crash-Folgemechanik (Engine `repairUntil` auswerten — Phase 9b)
- Audio-Konzept-Doc `audio-konzept-l1.md` aktualisieren mit neuen Lines
- L3 ist unangetastet, alte komplexe Konfig
- Atlas-Mail rausschicken zu Status: viel passiert ist, viele kleine Pläne laufen

## 2026-04-26/27 Sammel-Eintrag — Refill-Bugs, Audio-Plausibilität, Lenkzeit-Frage offen

### Refill / Aufträge-Liste (mehrere Iterationen)
Thomas hat wiederholt gemeldet "keine Aufträge" auf L2. Mehrere Bugs gefixt:

1. **Tick-Refill-Pfad** ([engine.js:421-424](../../logistik/engine.js#L421-L424)): Tick rief nur `_maybeRefillOffers` (L1-only). Geändert zu `_maybeGenerateFollowupContract` — routet selbst nach L1/L2.

2. **OPEN-Count vs. VISIBLE-Count** ([engine.js:2204-2253](../../logistik/engine.js#L2204-L2253)): UI zeigt OPEN+RESERVED, mein Refill zählte nur OPEN. Wenn alle OPEN ge-RESERVED wurden, blieb OPEN bei 0 → Liste leer trotz voller activeContracts. Jetzt VISIBLE-Count = OPEN+RESERVED.

3. **MIN_VISIBLE = max(cfgMax, 7)**: Hardcoded ≥ 7, auch wenn DB anderen Wert hat. SQL-Migration `2026-04-26-logistik-l2-maxcontracts-7.sql` erhöht DB von 5 auf 7. Lokal schon ausgeführt, Server noch nicht.

4. **MIN_OPEN_1C = 3**: Separater Loop garantiert ≥ 3 sichtbare 1-Container-Aufträge (für Cap-1-Trucks). `forceSingle` in `_generateL2ContractFromPool` erweitert auf OPEN+RESERVED-Zählung.

5. **Verfallene OPEN-Aufträge** ([engine.js:2275-2284](../../logistik/engine.js#L2275-L2284)): `_expireOffers` war L1-only. Auf L2 blieben verfallene OPEN in activeContracts, UI versteckte sie wegen `dueTime < now`, Refill zählte sie als "OK" → 0 sichtbar trotz technisch voller Liste. Jetzt entfernt L1+L2 verfallene OPEN aus dem Pool.

6. **Refill-Härte**: Try/catch um jede Generierung, einzelne Fehlversuche brechen den Loop NICHT mehr ab (nur `console.warn`+continue).

### Auto-Retoure + Vehicle-Loc-Bias
- Vehicle-Loc-Bias mehrfach justiert: 0.7 → 0.85 → 0.45 → **final 0.7**. Anna-in-Wien-Verlustgeschäfte-Problem gefixt: Bias war auf 0.45 zu niedrig.
- Auto-Retoure-Loop ([engine.js:2256-2280](../../logistik/engine.js#L2256-L2280)) garantiert pro IDLE-Standort **≥ 2 OPEN-1-Cont**, nicht nur 1.

### Tier-2-Pool erweitert
~28 neue Routen: AT/DE → Paris/Rotterdam/Hamburg/Warschau/Kopenhagen + Rückrouten. Damit ab Runde 3 echte Europatouren möglich. Im L2_OFFER_POOL [engine.js:1909-1968](../../logistik/engine.js#L1909-L1968).

### Autobahn-Game Cooldown + Pause
- 5-Min-Echtzeit-Cooldown zwischen zwei Autobahn-Spielen ([game.html:4091-4096](../../logistik/game.html#L4091-L4096)).
- Sim-Zeit pausiert hart über `LogistikEngine.pause(game)` statt nur `playerMode='pause'`. Grund: `convertRealTimeToGameMinutes` macht aus `timeScale=0` durch `(timeScale || 1)` ein 1× — Bug, der die Pause unwirksam machte. Korrekte Pause via `paused`-Flag.

### Online-Routen-Bug (Engine-Härte)
- `_normNet` ([engine.js:274-279](../../logistik/engine.js#L274-L279)): railnet/roadnet werden defensiv normalisiert. Crash "road.nodes is undefined" online war Symptom für fehlendes `lg-roadnet.json` am Server (PHP gab `[]` als Fallback). Jetzt kein Crash mehr, aber bis Atlas die Seeds deployt fahren LKWs Luftlinie.
- Atlas-Postfach: [_inbox/zentrale/2026-04-26-1456-logistik-seeds-fehlen-online.md](../zentrale/2026-04-26-1456-logistik-seeds-fehlen-online.md)

### Audio-Plausibilität — Bericht + Änderungen
Vollständiger Plausibilitäts-Check aller ~40 Voice-Lines gegen L1-Reglement durchgeführt. Probleme + Fixes:

- **Bug L2-Splash** ([game.html:3042](../../logistik/game.html#L3042)): "Ab 15 000€ → dritter LKW" entfernt — L2 hat keinen Auto-Buy, nur manuellen Kauf.
- **Bug Announce-Subtext** ([game.html:2768-2779](../../logistik/game.html#L2768-L2779)): "Mehr Aufträge gleichzeitig möglich" auf L1 falsch (max=1). Level-aware: L1 → "Du kannst jetzt zwei Touren parallel fahren".
- **all_negative_hint** ([game.html:1774-1780](../../logistik/game.html#L1774-L1780)): auf L1 stumm (Plural-Audio passt nicht zu max=1).
- **last_day** ([game.html:3238-3242](../../logistik/game.html#L3238-L3242)): Filter `totalDays !== 4` entfernt → spielt jetzt auch auf L1.
- **balance_low**: Neue Voice-Line + MP3 erzeugt. Trigger einmalig wenn Balance < 25% Startbudget ([game.html:3243-3251](../../logistik/game.html#L3243-L3251)).

### Driver-Sortierung
- IDLE-Fahrzeuge oben in der Fahrzeugliste ([game.html:2061-2068](../../logistik/game.html#L2061-L2068)). Stable-Sort.

### Verfahrene Fix
- Lokale Codeänderungen alle gemacht. Online ist Atlas zuständig — Postfach abgesetzt.

### OFFEN — wurde unterbrochen
**Lenkzeit-Pause-Frequenz-Frage von Thomas am 2026-04-27**: "kommt viel zu oft, das ist nicht möglich". Untersucht:
- L2+ only, MANDATORY_DRIVE_LIMIT_MIN = 270 (4,5h sim), MANDATORY_BREAK_MIN = 45.
- Counter `v.drivingMinutesSinceBreak` akkumuliert ÜBER Touren hinweg (resettet nur bei Pausen-Ende).
- Bei Auto-8x: 270 sim-min = 33 sec real → fühlt sich frequent an, ist aber EU-konform.
- Diskussion mit Thomas wurde durch das Auftrags-Problem unterbrochen.
- Offen: Counter beim Liefern (UNLOADING-Ende) zurücksetzen? Threshold auf 6h hochziehen?

### TODO offen
- Lenkzeit-Pause-Threshold mit Thomas klären (Reset bei Lieferung?)
- L2 maxActiveContracts=7 SQL auf Server deployen
- Atlas: lg-roadnet.json + lg-railnet.json deployen (Postfach 14:56)
- L3 noch unangetastet
- Crash-Folgemechanik `v.repairUntil` Engine-Auswertung (Phase 9b)
- Audio-Konzept-Doc aktualisieren (balance_low, last_day-Filter etc.)

## 2026-04-29 — iPad-Minigame-Fix (Parking)

Thomas-Meldung: Minispiel-Overlays passen nicht auf iPad-Display, kann das
Einparken nicht beenden. Pfeile gleichzeitig drücken geht nicht (kein
Multi-Touch-Komfort). Wunsch: Joystick-Steuerung wie der Heli (de facto hat
der Heli aber Map-Klicks, kein Joystick — selbst gebaut).

### Was geändert (nur Parking-Minispiel, [game.html](../../logistik/game.html))

1. **Horizontal-Layout auf Landscape ≥ 880px** ([:491-518](../../logistik/game.html#L491-L518)):
   - Modal-max-width 580px → 1100px im Landscape, max-height 92vh
   - Grid 2-spaltig: Canvas links (16:9), Steuerung rechts (280px)
   - Referenz-Bilder (Rückwärts/Vorwärts/Schief) im Landscape ausgeblendet
   - Hint-Text auf eine Zeile gekürzt
   - Modal hat jetzt `max-height: 94vh; overflow-y: auto` als Fallback

2. **Virtueller Joystick** ([:4297-4368](../../logistik/game.html#L4297-L4368)):
   - 200×200px Kreis, 80px Knob, dragbar via Pointer Events
   - Dead-Zone 18%, Achs-Threshold 32% — gibt 4 Achsen (auch diagonal)
   - Mappt auf bestehende `mgKeys`-Flags, Physik-Loop unverändert
   - setPointerCapture für sauberes Drag auch wenn Finger den Stick verlässt

3. **D-Pad bleibt parallel als Maus-Fallback**:
   - Touch (`@media (hover:none) and (pointer:coarse)`): D-Pad versteckt
   - Maus (`@media (hover:hover) and (pointer:fine)`): Joystick versteckt

### Nicht angefasst
- Shunting-Modal (Rangierbahnhof) — Tasten 1-6 + Klicks, kein Multi-Touch-Problem
- Autobahn-Game — externes vendor-Modul

## 2026-05-07 — Inbox-Check, 2 ungelesene Briefe von Atlas

Habe nach längerer Pause Inbox geprüft.

### 1. [2026-05-05 02:50 — Live-State-Hook](2026-05-05-0250-live-state-vergleichstabelle.md) — TODO
Atlas hat Live-View für Lehrkräfte gebaut. `pages/logistik.php` injiziert bereits
Heartbeat. Ich soll in [game.html](../../logistik/game.html) `window.GGS_LIVE_STATE`
als Funktion setzen, die ~1 KB JSON mit didaktisch sinnvollen Werten zurückgibt
(level, round, cash, profit, contractsOpen/Done/Lost, fleetSize, timeLeft).
Vorbild: [klima/game-2d.html:1317](../../klima/game-2d.html#L1317).
Außerdem an Atlas in `_inbox/zentrale/` melden, welche 4–8 Felder als Standard-
Vergleichsspalten in `LIVE_PRIMARY_FIELDS.logistik` hinterlegt werden sollen.

### 2. [2026-05-04 09:00 — Atlas Crash-Recovery](2026-05-04-0900-atlas-crash-recovery.md) — TODO
Atlas war nach Crash zurück, fragt nach offenem Status. Meine offenen Anfragen:
- 2026-04-26-1456: lg-roadnet.json + lg-railnet.json Server-Deploy (immer noch
  offen, sonst fahren LKWs online weiter Luftlinie)
- 2026-04-24-2330: Klassen-Ranking (Endscreen-Vergleich)
Status-Antwort an Atlas in `_inbox/zentrale/` schreiben.

### TODO offen aus 2026-05-07
- Live-State-Hook bauen + getestet
- Standard-Vergleichsspalten an Atlas melden
- Status-Antwort an Atlas (offen/erledigt-Liste)
- Lenkzeit-Pause-Threshold weiterhin offen
- Shunting-Minigame iPad-Layout-Check (nicht bestätigt, aber empfohlen)
