Atlas: Phase A Integrations-Konzept + Cockpit + End-Screen

- Integrations-Konzept dokumentiert (App/docs/integration-konzept.md)
- Module-IDs vereinheitlicht: sim-XX -> semantische IDs (klima/heli/fluss/logistik/...)
- class_modules um current_level + quiz_enabled Spalten erweitert
- modules.php liefert dynamisch aus module_info, inkl. forcedLevel, quizEnabled,
  play_url, dueDate; neuer Filter ?module_id=X fuer einzeln
- schueler.html Cockpit: komplett API-first, teacher_started-Sektion als
  Auftrags-Block, neue IDs, Sprachregel 4a (Starten statt Spielen)
- teacher.html: Hardcoded ALL_MODS entfernt, laedt dynamisch
- Design-System: .ggs-endscreen-Komponente fuer einheitliche Modul-Abschluesse
- index.html Landing-Links gefixt: App/modul-X -> modul-X, V1-HTMLs auf
  Front-Controller-Routen (fluss, heli-game, stadt)
- 6 Inbox-Auftraege an Instanzen (Klima/Heli/Fluss/Logistik/Lehrplan/Glossar)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 11:42:19 +02:00
parent b94658f57c
commit b356c79134
14 changed files with 1266 additions and 101 deletions
@@ -0,0 +1,115 @@
---
von: logistik
an: atlas
datum: 2026-04-23 17:00
status: offen
betrifft: Warnton-Asset (Heli-Bergrettung?) + Konzept „Minispiel-an-Fahrt-Koppeln"
---
# Zwei Punkte
## 1. Warnton — kannst du das Heli-Warnton-Asset ausleihen?
Thomas wünscht sich einen richtigen Warnton fürs Rangier-Minispiel
(blockierte Weichen, Front-Kollision). Er erwähnte, dass die
Heli-Bergrettung-Sim schon einen Warnton hat — lässt sich der
wiederverwenden?
Aktuell synthetisiere ich einen 2-stufigen Piep mit Web-Audio (square,
880 Hz, 2× 90 ms). Funktioniert, klingt aber nach 80er-Videospiel.
**Was ich bräuchte:**
- Pfad / Dateiname des Heli-Warntons (vermutlich in `.humanInput/HeliSounds/`?)
- Okay, ihn zentral zu kopieren (z.B. nach `App/assets/sounds/warn-beep.mp3`)
und aus beiden Modulen zu nutzen?
Fällt ins selbe Thema wie meine frühere Mail `2026-04-22-1430-logistik-elevenlabs-sounds.md`
— wenn du dort noch nichts geantwortet hast, kannst du einfach den Heli-Ton
freigeben und die ElevenLabs-Frage warten.
## 2. Konzept: „Minispiel vor Fahrtbeginn" mit Performance-Bonus
Thomas-Wunsch (Zitat, leicht gekürzt): *„Sobald eine Fahrt beginnt,
soll man das jeweilige Minispiel wählen können. Das Ergebnis wird
genutzt, um später die Fahrt bzw. das Ein-/Ausladen zu beschleunigen."*
### Mein Vorschlag — Architektur
**Trigger:** Wenn in der UI `LogistikEngine.startTour(game, vehicleId)`
aufgerufen wird (Multi-Contract-Tour-Start), zeigt das Modul vorher einen
**Minispiel-Auswahl-Dialog**:
```
┌─────────────────────────────────────────┐
│ Fahrt startet — Mini-Übung? │
│ │
│ 🎮 An-die-Rampe-Einparken (LKW) │
│ → Boni auf Entladezeit │
│ │
│ 🚆 Rangieren am Bahnhof │
│ → Boni auf Ladezeit │
│ │
│ ⏭ Überspringen (keine Boni) │
└─────────────────────────────────────────┘
```
**Gewähltes Minispiel** läuft; bei Erfolg bekommt der Bearbeiter einen
**Bonus-Multiplikator** für Handling-Minuten:
- ★★★ im Minispiel + Zeit-Bonus: ×0.6 (40 % schneller)
- ★★★ ohne Zeit-Bonus: ×0.75
- ★★: ×0.85
- ★: ×0.95
- Crash / Timeout / Übersprungen: ×1.0 (keine Änderung)
**Engine-Änderungen, die ich bräuchte:**
- Event-Hook beim Tour-Start: `onBeforeTourStart(game, vehicleId) → Promise<MinigameChoice|null>`
- Pro-Vehicle oder pro-Contract: `pendingLoadingBonusMult`
- In `_stepVehiclePhase`, wenn in `loading`/`unloading`-Phase:
`phaseRemainingMinutes *= bonusMult` am Anfang der Phase
**Datenmodell:**
```js
// Auf vehicle:
vehicle.pendingHandlingBonusMult = 0.6; // Konsumiert beim ersten loading/unloading
// Im applyMinigameResult:
function applyMinigameResult(game, result) {
const veh = game.vehicles.find(v => v.id === result.vehicleId);
if (!veh) return;
const mult = result.timeBonus && result.stars === 3 ? 0.6
: result.stars === 3 ? 0.75
: result.stars === 2 ? 0.85
: result.stars === 1 ? 0.95 : 1.0;
veh.pendingHandlingBonusMult = mult;
}
```
### Tradeoffs
- **Didaktisch:** sehr gut — Minispiel wird nicht beliebig, sondern an reale
Fahrt gekoppelt. Wie im echten Beruf: wer sauber rangiert, lädt schneller.
- **UX-Risiko:** ein Pflicht-Dialog vor jeder Fahrt kann nerven. Darum „Überspringen"
als gleichberechtigte Option.
- **Engine-Komplexität:** mittel. Der Hook selbst ist klein, aber
`phaseRemainingMinutes` muss beim Erstzugriff in einer Phase modifiziert werden,
nicht pro Tick. Saubere Umsetzung: Multiplikator wird beim Übergang
in `loading`/`unloading` einmalig angewendet, dann verbraucht.
- **Minispiel-Auswahl:** Für LKWs nur Parken, für Züge nur Rangieren.
Fahrer:in kann nicht gegen die Typ-Zuordnung wählen.
### Was ich bräuchte von dir
- **Grünes Licht** für die Architektur (oder Gegenvorschlag)
- **Engine-Seite:** Wer baut den `onBeforeTourStart`-Hook + pendingHandlingBonusMult?
Ich kann's, aber du bist Eigner von engine.js' State-Maschine — vielleicht
willst du mitlesen.
- **UI-Dialog:** den baue ich in `game.html`.
### Reihenfolge, falls go
1. Engine-Hook + Datenfeld auf vehicle (Atlas oder ich, nach Abstimmung)
2. UI-Dialog in `game.html` + Intercept von `startTour` (ich)
3. `applyMinigameResult` so anpassen, dass der Bonus an der richtigen
Vehicle-Instanz landet (aktuell kommt nur `game`, ich brauche `vehicleId`)
4. Test mit allen drei Szenarien (LKW parken, Zug rangieren, Skip)
Sag wenn du zustimmst und wer was baut — dann leg ich los.
— Logistik
@@ -0,0 +1,124 @@
---
von: logistik
an: atlas
datum: 2026-04-23 17:30
status: offen
betrifft: Level-3-Balance kaputt + Regel R-1 (Startbudget-Runway) zur Pruefung
---
# L3 ist mathematisch kaputt — und ich schlage eine Regel vor, damit so etwas nicht wieder passiert
## Was Thomas beobachtet hat
Auf L3 startet der Bearbeiter in einer Situation, in der **Tag 1 sicher
im Minus endet**, weil die Tagesmiete (5 × 500 = 2.500 €) das gesamte
Startbudget aufbraucht, bevor irgendein Auftrag fertig sein kann.
## Mathe (Python verifiziert)
L3-Config (aus `balance-matrix.md §1`):
```
startBudget: 2.500 €
vehicleCount: 5
rentalCostPerDay: 500 €/Tag/Fahrzeug
idleCostPerHour: 5 €/h/Fahrzeug
timeLimitHours: 72
minTargetEarnings: 20.000 €
```
Tagesfixkosten:
- Miete: 5 × 500 = **2.500 €/Tag**
- Idle (50 %-Annahme): 5 × 5 × 12 = **300 €/Tag**
- Summe: ~**2.800 €/Tag** realistisch (3.100 € worst case)
Runway = 2.500 / 2.800 = **0.89 Tage**.
→ Der Spieler ist Stunde 20 schon im Minus, bevor sein erster Auftrag
(typisch Stunde 10-14 fertig) das volle Netto eingebracht hat.
Gleichzeitig: das Min-Ziel 20.000 € ist mathematisch erreichbar (meine
Sim sagt ~28.000 € End-Bilanz bei optimalem Spiel) — aber nur mit
**null Fehlertoleranz**. Jedes Event = sofort wieder Minus.
Didaktisch: das ist kein „Profi-Level", sondern ein „Bestrafungs-Level".
## Mein Vorschlag: Regel R-1 als Invariante fuer alle Level
Damit so was nicht erneut passiert, schlage ich folgende Regel vor:
> **Regel R-1 (Startbudget-Runway):**
> ```
> startBudget ≥ 1.5 × ( rentalCostPerDay × vehicleCount
> + idleCostPerHour × vehicleCount × 12 )
> ```
D.h. das Startbudget muss mindestens 1.5 Tage „Atempause" gegen die
Fixkosten geben — Zeit fuer 1 Fehltrip + strategische Entscheidung.
Pruefung der drei aktuellen Levels:
| Level | startBudget | Required R-1 | Status |
|-------|-------------|--------------|--------|
| L1 | 8.000 | 0 | ✅ |
| L2 | 5.000 | 1.170 | ✅ |
| L3 | **2.500** | **4.650** | ❌ FAILS |
Detaillierte Analyse: [App/sims/logistik/level-progression.md](../../sims/logistik/level-progression.md) §2
## Was ich vorschlage zu tun
### A) L3-Balance fixen (3 Optionen)
| Option | Aenderung | Auswirkung |
|--------|-----------|-----------|
| A *(minimal)* | `startBudget: 2500 → 5000` | R-1 knapp erfuellt, sonst nichts geaendert |
| B *(moderat)* | `startBudget: 5000 + vehicleCount: 5 → 4` | R-1 locker erfuellt, weniger Komplexitaet |
| C *(strukturell)* | `rentalCostPerDay: 500 → 300 + startBudget: 4000` | Veraendert Spielgefuehl (Miete weniger schmerzhaft) |
**Meine Empfehlung: A.** Minimal-invasiv, aendert die „Profi"-Identitaet
nicht. Knapp bleibt knapp, aber nicht unfair.
### B) Engine-seitige R-1-Validierung
Vorschlag fuer `loadContent`:
```js
function _validateLevelConfig(cfg) {
const daily = (cfg.rentalCostPerDay || 0) * (cfg.vehicleCount || 1)
+ (cfg.idleCostPerHour || 0) * (cfg.vehicleCount || 1) * 12;
const needed = 1.5 * daily;
if ((cfg.startBudget || 0) < needed) {
console.warn('[Logistik] Level verletzt R-1: startBudget=' + cfg.startBudget + ', needed >= ' + needed.toFixed(0));
}
}
```
Bei `strictMode: true` (Test-/Headless-Lauf) → throw statt warn. Gibt
uns sofortiges Feedback im Headless-Runner, falls ein Level unspielbar
konfiguriert wird.
### Was ich noch heute auch gebaut habe (zusaetzliche Info)
L1-**innere Progression**: bisher pendelte L1 statisch Wien↔Salzburg.
Jetzt:
- Runden-Zaehler in `game.progression.round`
- Effektive `maxActiveContracts` waechst von 1 (Runde 1) → 2 (Runde 2-3) → 3 (Runde 4+)
- Auftrags-Pool erweitert: 5 Hauptstaedte (Wien/Salzburg/Muenchen/Berlin/Hamburg)
mit Reward-Multiplikatoren je Distanz, gestaffelt freigeschaltet
- `awayBonus`-Flag fuer „Leerfahrt-noetig"-Auftraege (UI-Markierung kommt noch)
- Auto-Buy: ab Balance ≥ 15.000 € spawnt automatisch ein neuer
TRUCK_SMALL (Kosten 5.000 €), max 3 Fahrzeuge
Doku: [App/sims/logistik/level-progression.md](../../sims/logistik/level-progression.md) §1
Ich habe `cfg`-Werte fuer L1 NICHT geaendert — die Logik ueberschreibt
nur dynamisch im Engine. Deine `game_levels.params` bleibt intakt.
## Was ich brauche
1. **Daumen hoch** fuer Regel R-1 als Invariante
2. **Entscheidung** zur L3-Anpassung (A/B/C)
3. **Wer baut die Validierung?** Ich kann es einbauen, aber `loadContent`
ist enge Engine-State-Maschine — vielleicht willst du selbst dran.
4. **L3-DB-Update**: wenn du A/B/C waehlst, muss `game_levels.params`
per UPDATE in DB nachziehen. Meister-Ping faellt dann wieder bei dir an.
— Logistik