# Balance-Korridor (Regeln R-1, R-2, R-3)

**Stand:** 2026-04-24
**Gilt für:** Logistik Europa (Modul 11), Vorlage für andere Sim-Module

Dieses Dokument formalisiert den Balance-Anspruch („jedes Level muss man
gewinnen UND verlieren können") als drei Regeln und beschreibt eine
mechanische Methode, sie zu prüfen.

---

## 1. Anspruch

> **Ein Level ist balanciert, wenn:**
> 1. Der/die Bearbeiter:in **Zeit zum Nachdenken** hat, bevor sie ins
>    Minus rutscht (R-1 Runway).
> 2. Mit gutem Spiel **gewinnen kann** (R-2 Winnable).
> 3. Mit schlechtem oder keinem Spiel **verlieren kann** (R-3 Loseable).
>
> Keine der drei Bedingungen darf systematisch verfehlt werden — sonst
> ist das Level entweder Frustquelle (kann nur verlieren) oder Pseudo-
> Lernspiel (kann nur gewinnen, keine Konsequenz).

---

## 2. Regel R-1 — Startbudget-Runway

Bereits in [`level-progression.md` §2](level-progression.md) hergeleitet.
Hier nur die Formel:

```
startBudget ≥ 1.5 × ( rentalCostPerDay × vehicleCount
                   + idleCostPerHour   × vehicleCount × 12 )
```

**Interpretation:** Anfangsbudget reicht für 1.5 Tage Operating ohne
Einnahmen. Gibt Puffer für Fehltrip + strategische Entscheidung.

---

## 3. Regel R-2 — Winnable

> Bei **optimalem oder mindestens guten** Spiel muss der Level
> spätestens beim **dritten Versuch** mit positivem End-Saldo gegenüber
> `minTargetEarnings` enden.

**Formal (Median über 5 Seeds):**
- L1: `runLevel(1, *, naive)`   → `successful: true` AND `endBalance − startBudget ≥ +1.000 €`
- L1: `runLevel(1, *, greedy)`  → `successful: true` AND `endBalance − startBudget ≥ +2.000 €`
- L2: `runLevel(2, *, greedy)`  → `successful: true` AND `endBalance − startBudget ≥ 0`
- L2: `runLevel(2, *, optimal)` → `successful: true` AND `endBalance − startBudget ≥ +5.000 €`
- L3: `runLevel(3, *, optimal)` → `successful: true` AND `endBalance − startBudget ≥ 0`

(Werte aus `balance-matrix.md` §3 übernommen.)

**Was wir gerade messen können:**
- L1: ✓ naive, greedy, optimal laufen — alle prüfbar
- L2: ✓ greedy + optimal laufen — prüfbar
- L3: ✓ optimal läuft (Best-Net-Heuristik seit 2026-04-24) — prüfbar

→ `optimal` ist keine echte globale Optimierung (VRP-TW ist NP-hart),
sondern eine **Best-Net-Heuristik**: pro Tick wird die (Contract, Vehicle)-
Paarung mit maximalem erwarteten Netto ausgewählt
(reward × 1.15 − pickup-fare − delivery-fare − erwartete Verspätungsstrafe).
Dominiert greedy in allen drei Leveln und reicht als Proxy für „gutes Spiel".

---

## 4. Regel R-3 — Loseable

> **Untätigkeit oder grobes Fehlspiel** muss zuverlässig zum Verlieren
> führen, sonst ist das Level keine Lernsituation.

**Formal (Median über 5 Seeds):**
- L1: `runLevel(1, *, noop)` → `successful: false` AND `endBalance < minTargetEarnings`
- L2: `runLevel(2, *, noop)` → `successful: false`
- L3: `runLevel(3, *, noop)` → `successful: false`
- L2: `runLevel(2, *, naive)` → `successful: false` (zusätzlich gefordert: schlechte Disposition reicht nicht)
- L3: `runLevel(3, *, naive)` → `successful: false`
- L3: `runLevel(3, *, greedy)` → `successful: false` ODER `knapp negativ`

**Implizite Anforderung:** Auch ohne Untätigkeit muss der **Schweregrad
mit Level steigen**. L3 darf nicht durch greedy lösbar sein (sonst ist
es nur ein L2-Klon).

---

## 5. Aktuelle Verifikation (Stand 2026-04-24)

### R-1 (mathematisch)

| Level | Required ≥ | Hat | Verdikt |
|-------|-----------|-----|---------|
| L1    | 0         | 8.000 | ✅ |
| L2    | 1.170     | 5.000 | ✅ |
| L3    | 4.650     | **5.000** *(nach Fix vom 2026-04-24)* | ✅ |

### R-2 / R-3 (per Headless-Runner — siehe [headless-runner.html](headless-runner.html))

```
Status: VOLLSTAENDIG
- noop, naive, greedy, optimal laufen alle
- Korridor-Check-Button in headless-runner.html pruft R-1 + R-2 + R-3
  ueber alle 60 Durchlaeufe (3 Lvl × 4 Strategien × 5 Seeds)
```

Konkrete Runs noch ausständig — beim Server-/Browser-Test mit
[headless-runner.html](http://localhost/geograsim/App/sims/logistik/headless-runner.html)
„Balance-Korridor-Check"-Button drücken (kommt mit dem Patch dieses Dokuments).

---

## 6. Methode zur Sicherstellung („was tun, wenn ein Level neu konfiguriert wird")

### 6a. Pre-Deploy-Check (vor jeder Level-Param-Änderung)

1. **R-1 mit Taschenrechner**: Formel oben rechnen, muss ≥ erfüllt sein.
   Engine-seitig schon eingebaut: `_validateRuleR1` in `loadContent` loggt
   `console.warn` bei Verletzung.
2. **R-2/R-3 via Headless-Runner**: `headless-runner.html` öffnen,
   „Balance-Korridor-Check" drücken. Ergebnis-Tabelle vergleichen mit
   Soll-Korridor (siehe §3 + §4).
3. **Bei Verletzung**: Param ändern (üblicherweise `startBudget`,
   `minTargetEarnings`, `eventProbabilityMultiplier`), erneut prüfen.
4. **Erst dann committen**.

### 6b. Continuous Verification (perspektivisch, sobald Atlas freigibt)

- Test-Suite-Script (Node.js): `npm run balance-check` führt
  `LogistikRunner.runMatrix({levels:[1,2,3], strategies:[...], seeds:[1..5]})`
  und vergleicht gegen Korridor. Exit-Code 0 = pass, 1 = fail.
- CI-Hook beim Push: blockiert Merge, wenn Korridor nicht erreicht.
- Voraussetzung: Node-Engine-Export (engine.js läuft schon UMD-konform).

### 6c. Manueller Sanity-Check (ergänzend)

Reine Zahlen reichen nicht — der „Spielspaß" ist nicht messbar.
Nach jedem Param-Change zusätzlich:
- 1× Live-Test im Browser pro Level mit naive Spielstil (mehrere
  Fehltrips).
- 1× Test mit gezielt-strategischem Spielstil.
- Fühlt sich der Schwierigkeitsgrad richtig an, oder kommt Frust auf?

Die mechanischen Regeln R-1/R-2/R-3 sind **notwendig, aber nicht
hinreichend**.

---

## 7. Offene Punkte

### Kurzfristig (vor dem Schul-Pilot)
- **`optimal`-Strategie** als Best-Net-Heuristik implementiert
  (2026-04-24, headless-runner.js, 120 Zeilen). Kriterium pro Tick:
  max `reward × 1.15 − pickup-fare − delivery-fare − erwartete Strafe`,
  dann feasible Zuweisungen in Sortierreihenfolge feuern.
- **„Balance-Korridor-Check"-Button** in `headless-runner.html`
  einbauen (erledigt, prueft R-1/R-2/R-3 mechanisch).

### Mittelfristig
- **Node-Test-Script** mit Exit-Code für CI.
- **Per-Modul-Generalisierung**: das Korridor-Pattern in
  `App/docs/integration-konzept.md` als Plattform-Vertrag dokumentieren,
  damit jedes Modul (Klima, Heli, …) die gleiche Disziplin hat.

### Langfristig
- **Stochastische Robustheit**: Korridor nicht nur über 5 Seeds
  prüfen, sondern Konfidenzintervall (z.B. 95 % über 100 Seeds).
  Aktuell nicht nötig — die Engine ist deterministisch und 5 Seeds
  geben uns schon eine gute Stichprobe.
