<template>
  <div class="contenthidden">
    <div class="contentscroll">
      <!-- Spiel-Titel -->
      <div class="game-title">
        <h1>{{ $t('minigames.taxi.title') }}</h1>
        <p>{{ $t('minigames.taxi.description') }}</p>
      </div>

      <!-- Spiel-Layout -->
      <div class="game-layout">
        <!-- Spielbrett (links) -->
        <div class="game-board-section">
          <!-- Spielbereich mit Canvas und Legende -->
          <div class="game-area">
            <!-- Legende (links) -->
            <div class="controls-legend">
              <h3>Steuerung</h3>
              <div class="legend-grid">
                <div class="legend-item">
                  <span class="legend-key">↑ W</span>
                  <span class="legend-text">Gas geben</span>
                </div>
                <div class="legend-item">
                  <span class="legend-key">↓ X</span>
                  <span class="legend-text">Bremsen</span>
                </div>
                <div class="legend-item">
                  <span class="legend-key">→ D</span>
                  <span class="legend-text">Rechts lenken</span>
                </div>
                <div class="legend-item">
                  <span class="legend-key">← A</span>
                  <span class="legend-text">Links lenken</span>
                </div>
              </div>
              
              <!-- Ziele -->
              <div class="game-objectives">
                <h4>Ziele</h4>
                <ul>
                  <li>Vermeide Kollisionen mit anderen Fahrzeugen</li>
                </ul>
              </div>

              <!-- Straßenname-Legende -->
              <div class="game-objectives" v-if="streetLegend.length">
                <h4>Straßennamen</h4>
                <ul>
                  <li 
                    v-for="item in streetLegend" 
                    :key="item.num"
                    class="legend-street-item"
                    :class="{ selected: selectedStreetName === item.name }"
                    @click="onSelectStreet(item.name)"
                  >
                    {{ item.num }}: {{ item.name }}
                  </li>
                </ul>
              </div>
            </div>

            <!-- Spiel-Canvas mit Tacho -->
            <div class="game-canvas-section">
              <!-- Tacho-Display -->
              <div class="tacho-display">
                <div class="tacho-speed">
                  <span class="lives" title="Verbleibende Fahrzeuge">❤️ {{ vehicleCount }}</span>
                  <span class="fuel" title="Treibstoff">⛽ {{ Math.round(fuel) }}%</span>
                  <span class="score" title="Punkte">⭐ {{ score }}</span>
                  <span class="speed-violations" title="Geschwindigkeitsverstöße">📷 {{ speedViolations }}</span>
                  <span class="redlight-counter" title="Rote Ampeln überfahren">
                    <span class="redlight-icon">🚦</span>
                    <span class="redlight-value">{{ redLightViolations }}</span>
                  </span>
                  <span class="speed-group">
                    <span class="tacho-icon">⏱️</span>
                    <span class="speed-value">{{ taxi.speed * 5 }}</span>
                    <span class="speed-unit">km/h</span>
                  </span>
                </div>
              </div>
              
              <!-- Pause-Anzeige -->
              <div v-if="showPauseOverlay" class="pause-overlay">
                <div class="pause-message">
                  <h2>{{ $t('minigames.taxi.paused') }}</h2>
                  <button @click="togglePause" class="resume-button">
                    {{ $t('minigames.taxi.resume') }}
                  </button>
                </div>
              </div>

              <!-- Canvas und Highscore Container -->
              <div class="game-canvas-section">
                <!-- Canvas (immer sichtbar) -->
                <div class="game-canvas-container">
                  <canvas 
                    ref="gameCanvas" 
                    width="500" 
                    height="500"
                    class="game-canvas"
                    @click="handleCanvasClick"
                    @keydown="handleKeyDown"
                    tabindex="0"
                  ></canvas>
                </div>
                
                <!-- Highscore-Anzeige (als Overlay über dem Canvas) -->
                <div v-if="showHighscore" class="highscore-overlay">
                <div class="highscore-header">
                  <h2>🏆 Highscore</h2>
                  <div class="highscore-subtitle">Top 20 Spieler</div>
                </div>
                <div class="highscore-list">
                  <div v-if="loadingHighscore" class="loading-message">
                    Lade Highscore...
                  </div>
                  <div v-else-if="highscoreList.length === 0" class="no-highscore">
                    Noch keine Highscores vorhanden
                  </div>
                  <div v-else class="highscore-table">
                    <div 
                      v-for="(entry, index) in highscoreList" 
                      :key="index"
                      class="highscore-entry"
                      :class="{ 'current-player': entry.isCurrentPlayer }"
                    >
                      <div class="highscore-rank">{{ entry.rank }}</div>
                      <div class="highscore-name">{{ entry.nickname }}</div>
                      <div class="highscore-points">{{ entry.points }} Pkt</div>
                    </div>
                    <div v-if="showCurrentPlayerBelow" class="highscore-separator">...</div>
                    <div 
                      v-if="currentPlayerEntry && showCurrentPlayerBelow"
                      class="highscore-entry current-player"
                    >
                      <div class="highscore-rank">{{ currentPlayerEntry.rank }}</div>
                      <div class="highscore-name">{{ currentPlayerEntry.nickname }}</div>
                      <div class="highscore-points">{{ currentPlayerEntry.points }} Pkt</div>
                    </div>
                  </div>
                </div>
                </div>
              </div>
            </div>
          </div>

          <!-- Spiel-Controls -->
          <div class="game-controls">
            <button @click="togglePause" class="control-button">
              {{ isPaused ? $t('minigames.taxi.resume') : $t('minigames.taxi.pause') }}
            </button>
            <button @click="restartLevel" class="control-button">
              {{ $t('minigames.taxi.restartLevel') }}
            </button>
            <button @click="toggleHighscore" class="control-button">
              {{ showHighscore ? 'Zurück zum Spiel' : 'Highscore' }}
            </button>
          </div>
        </div>

        <!-- Sidebar (rechts) -->
        <div class="sidebar-section">
          <!-- Minimap -->
          <div class="minimap-card">
            <div class="minimap-header">
              <h3 class="minimap-title">Minimap</h3>
              <div class="map-selector">
                <select v-model="selectedMapId" @change="onMapChange" class="map-select">
                  <option 
                    v-for="map in maps" 
                    :key="map.id" 
                    :value="map.id"
                  >
                    {{ map.name }}
                  </option>
                </select>
              </div>
            </div>
            <div class="minimap-container">
              <canvas 
                ref="minimapCanvas" 
                width="200" 
                height="150"
                class="minimap-canvas"
              ></canvas>
            </div>
          </div>

          <!-- Geladene Passagiere -->
          <div class="loaded-passengers-card">
            <div class="loaded-passengers-header">
              <h3 class="loaded-passengers-title">Geladene Passagiere</h3>
            </div>
            <div class="loaded-passengers-content">
              <div v-if="loadedPassengersList.length === 0" class="no-passengers">
                Keine Passagiere im Taxi
              </div>
              <table v-else class="passengers-table">
                <thead>
                  <tr>
                    <th>Name</th>
                    <th>Ziel</th>
                    <th>Bonus</th>
                    <th>Zeit</th>
                  </tr>
                </thead>
                <tbody>
                  <tr 
                    v-for="(passenger, index) in loadedPassengersList" 
                    :key="index" 
                    class="passenger-row"
                    :class="{ 'time-warning': passenger.timeLeft <= 10, 'time-critical': passenger.timeLeft <= 5 }"
                  >
                    <td class="passenger-name-cell">{{ passenger.name }}</td>
                    <td class="passenger-destination-cell">{{ passenger.destination.location }}</td>
                    <td class="passenger-bonus-cell">{{ passenger.bonusData ? passenger.bonusData.bonus : 0 }}</td>
                    <td class="passenger-time-cell" :class="{ 'time-warning': passenger.timeLeft <= 10, 'time-critical': passenger.timeLeft <= 5 }">
                      {{ passenger.timeLeft }}s
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>

          <!-- Wartende Passagiere -->
          <div class="waiting-passengers-card">
            <div class="waiting-passengers-header">
              <h3 class="waiting-passengers-title">Wartende Passagiere</h3>
            </div>
            <div class="waiting-passengers-list">
              <div v-if="waitingPassengersList.length === 0" class="no-passengers">
                Keine wartenden Passagiere
              </div>
              <table v-else>
                <tr v-for="(passenger, index) in waitingPassengersList" 
                :key="index" >
                  <td><b>{{ passenger.name }}</b><span v-if="getPassengerTimeLeft(passenger) <= 5" class="passenger-timer"> ({{ getPassengerTimeLeft(passenger) }}s)</span></td>
                  <td>{{ passenger.location }}</td>
                </tr>
              </table>
            </div>
          </div>


        </div>
      </div>
    </div>
    
  </div>
</template>

<script>
import streetCoordinates from '../../utils/streetCoordinates.js';
import { MotorSound, NoiseGenerator } from '../../assets/motorSound.js';
import apiClient from '../../utils/axios.js';
export default {
  name: 'TaxiGame',
  components: {
  },
  data() {
    return {
      isPaused: false,
      showPauseOverlay: false,
      score: 0,
      money: 0,
      passengersDelivered: 0,
      waitingPassengersList: [],
      loadedPassengersList: [], // Aktuell geladene Passagiere im Taxi
      occupiedHouses: new Set(), // Verhindert doppelte Belegung von Häusern
      lastPassengerGeneration: 0,
      debugCollisionShown: false, // Flag für einmalige Debug-Ausgabe
      passengerGenerationInterval: 0,
      // Timeout-Referenzen für Cleanup
      passengerGenerationTimeout: null,
      crashDialogTimeout: null,
      bonusMultiplier: 15, // Bonus pro Tile
      timePerTile: 8, // Sekunden pro Tile
      fuel: 100,
      isRefueling: false,
      refuelTimer: null,
      currentLevel: 1,
      gameRunning: false,
      crashes: 0,
      gameLoop: null,
      lastSpeedChange: 0,
      speedChangeInterval: 100, // 200ms = 0.2 Sekunden
      lastSteerChange: 0,
      steeringBaseInterval: 300, // Basisintervall in ms bei sehr niedriger Geschwindigkeit
      canvas: null,
      ctx: null,
      minimapCanvas: null,
      minimapCtx: null,
      taxi: {
        x: 250, // Mitte des 400x400px Tiles
        y: 250, // Mitte des 400x400px Tiles
        width: 30, // Skalierte Breite (50px Höhe * 297/506)
        height: 50, // 50px Höhe
        angle: 0, // Fahrtrichtung
        imageAngle: Math.PI / 2, // Bildrotation (90° nach rechts)
        speed: 0,
        maxSpeed: 24, // 24 = 120 km/h (5er-Schritte: 0, 5, 10, ..., 120)
        image: null // Taxi-Bild
      },
      currentTile: {
        row: 0,
        col: 0
      },
      tiles: {
        size: 500, // 400x400px pro Tile
        images: {}
      },
      houseImage: null,
      passengerImages: {}, // Speichert geladene Passagier-Bilder
      trafficLightStates: {},
      lastTrafficLightTick: 0,
      redLightViolations: 0,
      redLightLastCount: {},
      lastViolationSound: 0,
      lastMinimapDraw: 0,
      minimapDrawInterval: 120,
      lastPassengerTimerUpdate: 0, // Throttling für Passagier-Timer-Updates
      radarImg: null,
      activeRadar: false,
      radarAtTopEdge: true, // legacy flag (nicht mehr genutzt)
      radarTicketShown: false,
      speedViolations: 0,
      radarAxis: null, // 'H' (horizontale Messlinie: y=konstant) oder 'V' (vertikale Messlinie: x=konstant)
      radarLinePos: 0,
      vehicleCount: 5,
      redLightSincePenalty: 0,
      gameStartTime: null, // Zeitstempel wann das Spiel gestartet wurde
      maps: [], // Geladene Maps aus der Datenbank
      currentMap: null, // Aktuell verwendete Map
      selectedMapId: null, // ID der ausgewählten Map
      passengers: [],
      destinations: [],
      obstacles: [],
      keys: {},
      motorSound: null,
      audioContext: null,
      audioUnlockHandler: null,
      selectedStreetName: null
      ,motorStopTimeout: null
      ,houseNumbers: {}
      ,prevTaxiX: 250
      ,prevTaxiY: 250
      ,skipRedLightOneFrame: false
      ,gasStations: [] // Tankstellen im Spiel
      ,showHighscore: false // Highscore-Anzeige aktiv
      ,highscoreList: [] // Liste der Highscore-Einträge
      ,loadingHighscore: false // Lade-Status für Highscore
      ,currentPlayerEntry: null // Eintrag des aktuellen Spielers
      ,showCurrentPlayerBelow: false // Zeige aktuellen Spieler nach Platz 20
      // Autos-System
      ,cars: [] // Liste der aktiven Autos
      ,carImage: null // Geladenes Auto-Bild
      ,lastCarGeneration: 0 // Zeitstempel der letzten Autos-Generierung
      ,carGenerationInterval: 1000 // Autos-Generierung alle 1000ms (1 Sekunde) prüfen
      ,carSpawnProbability: 0.2 // 20% Wahrscheinlichkeit pro Sekunde
    }
  },
  computed: {
    toggleIcon() {
      return this.isStatsExpanded ? '−' : '+';
    },
    streetLegend() {
      // Sammle alle Straßennamen aus currentMap.tileStreets und vergebe laufende Nummern
      try {
        const legend = [];
        if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return legend;
        const nameToNum = new Map();
        let counter = 1;
        for (const ts of this.currentMap.tileStreets) {
          if (ts.streetNameH && ts.streetNameH.name) {
            const n = ts.streetNameH.name;
            if (!nameToNum.has(n)) nameToNum.set(n, counter++);
          }
          if (ts.streetNameV && ts.streetNameV.name) {
            const n = ts.streetNameV.name;
            if (!nameToNum.has(n)) nameToNum.set(n, counter++);
          }
        }
        // Sortiert nach zugewiesener Nummer
        const entries = Array.from(nameToNum.entries()).sort((a,b) => a[1]-b[1]);
        for (const [name, num] of entries) {
          legend.push({ num, name });
        }
        return legend;
      } catch (e) {
        return [];
      }
    }
  },
  async mounted() {
    this.initializeGame();
    this.initializeMinimap();
    this.loadTiles();
    this.loadTaxiImage();
    this.loadHouseImage();
    this.loadPassengerImages();
    this.loadCarImage();
    this.loadMaps();
    this.setupEventListeners();
    await this.initializeMotorSound();
    this.setupAudioUnlockHandlers();
    this.lastTrafficLightTick = Date.now();
    
    // Memory-Cleanup seltener ausführen, um Render-Glitches zu vermeiden
    this.memoryCleanupInterval = setInterval(() => {
      this.performMemoryCleanup();
    }, 2 * 60 * 1000); // alle 2 Minuten
  },
  beforeUnmount() {
    console.log('🚪 Component unmounting, cleaning up...');
    this.cleanup();
  },
    methods: {
    // Ampelschaltung: sekündliche Phasen-Updates; pro Tile ein State
    updateTrafficLights() {
      const now = Date.now();
      if (now - this.lastTrafficLightTick < 1000) return;
      this.lastTrafficLightTick = now;
      if (!this.currentMap || !Array.isArray(this.currentMap.tiles)) return;
      const tiles = this.currentMap.tiles.filter(t => t.trafficLight || (t.meta && t.meta.trafficLight));
      for (const t of tiles) {
        const key = `${t.x},${t.y}`;
        let st = this.trafficLightStates[key];
        if (!st) {
          // initialisieren: Phase starten, zufällige Grün/Rot-Dauern 5..15s
          const r = (min,max)=> Math.floor(min + Math.random()*(max-min+1));
          st = {
            phase: 0,            // 0:Hgrün/Vrot, 1:Hgelb/Vrotgelb, 2:Hrot/Vgrün, 3:Hrotgelb/Vgelb
            remaining: r(5,15),  // Sekunden bis zur nächsten Umschaltung
            hDur: r(5,15),       // Dauer H-grün (Phase 0)
            vDur: r(5,15)        // Dauer V-grün (Phase 2)
          };
          this.trafficLightStates[key] = st;
        }
        st.remaining -= 1;
        if (st.remaining <= 0) {
          // Phase vorwärts schalten, Zwischenphasen 1s
          if (st.phase === 0) { st.phase = 1; st.remaining = 1; }
          else if (st.phase === 1) { st.phase = 2; st.remaining = st.vDur; }
          else if (st.phase === 2) { st.phase = 3; st.remaining = 1; }
          else { st.phase = 0; st.remaining = st.hDur; }
        }
      }
    },
    // Hilfsfunktion: Liefert laufende Nummer für gegebenen Straßennamen
    getStreetNumberByName(name) {
      const item = this.streetLegend.find(it => it.name === name);
      return item ? item.num : null;
    },

    // Zeichnet die Straßen-Nr. auf die Minimap, je nach Tile-Typ und Position (pro Name nur einmal)
    drawStreetNumbersOnMinimap(ctx, x, y, size, tileType, col, row, drawnNames) {
      if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;

      // Finde etwaige StreetName-Einträge für dieses Tile
      const entry = this.currentMap.tileStreets.find(ts => ts.x === col && ts.y === row);
      if (!entry) return;

      ctx.save();
      const baseFontSize = Math.max(9, Math.floor(size * 0.22) - 1);
      ctx.font = `${baseFontSize}px sans-serif`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';

      const left = x + size * 0.2;
      const right = x + size * 0.8;
      const top = y + size * 0.2;
      const bottom = y + size * 0.8;

      const hName = entry.streetNameH?.name || null;
      const vName = entry.streetNameV?.name || null;
      const hNum = hName ? this.getStreetNumberByName(hName) : null;
      const vNum = vName ? this.getStreetNumberByName(vName) : null;

      const t = tileType;
      // Regeln gemäß Vorgabe
      // Horizontal linke Seite:
      const hLeftTiles = new Set(['cornerbottomleft','cornertopleft','horizontal','tdown','tup','cross','fuelhorizontal','tleft']);
      // Horizontal rechte Seite:
      const hRightTiles = new Set(['cornerbottomright','cornertopright','tright']);
      // Vertical oben:
      const vTopTiles = new Set(['cornertopleft','cornertopright','vertical','tup','tleft','tright','cross','fuelvertical']);
      // Vertical unten:
      const vBottomTiles = new Set(['cornerbottomleft','cornerbottomright','tdown']);

      // Zusätzliche Kanten-spezifische Regeln aus der Einleitung
      // cornerbottomright: horizontal rechts, vertical unten
      // cornerbottomleft: horizontal links, vertical unten
      // ... ist bereits über Sets abgedeckt

      if (hNum !== null && hName && !drawnNames.has(hName)) {
        const hx = hRightTiles.has(t) ? right : left;
        const hy = y + size * 0.5 + 1; // 1px nach unten
        const isSelH = (this.selectedStreetName && this.selectedStreetName === hName);
        ctx.fillStyle = isSelH ? '#ffffff' : '#111';
        ctx.font = isSelH ? `bold ${baseFontSize}px sans-serif` : `${baseFontSize}px sans-serif`;
        ctx.fillText(String(hNum), hx, hy);
        drawnNames.add(hName);
      }

      if (vNum !== null && vName && !drawnNames.has(vName)) {
        const vx = x + size * 0.5;
        const vy = vTopTiles.has(t) ? top : (vBottomTiles.has(t) ? bottom : top);
        const isSelV = (this.selectedStreetName && this.selectedStreetName === vName);
        ctx.fillStyle = isSelV ? '#ffffff' : '#111';
        ctx.font = isSelV ? `bold ${baseFontSize}px sans-serif` : `${baseFontSize}px sans-serif`;
        ctx.fillText(String(vNum), vx, vy);
        drawnNames.add(vName);
      }

      ctx.restore();
    },
      // Mapping zwischen Spiel-Tile-Namen (lowercase) und streetCoordinates.json (camelCase)
      mapTileTypeToStreetCoordinates(tileType) {
        const mapping = {
          'cornertopleft': 'cornerTopLeft',
          'cornertopright': 'cornerTopRight', 
          'cornerbottomleft': 'cornerBottomLeft',
          'cornerbottomright': 'cornerBottomRight',
          'horizontal': 'horizontal',
          'vertical': 'vertical',
          'cross': 'cross',
          'fuelhorizontal': 'fuelHorizontal',
          'fuelvertical': 'fuelVertical',
          'tleft': 'tLeft',
          'tright': 'tRight',
          'tup': 'tUp',
          'tdown': 'tDown'
        };
        return mapping[tileType] || tileType;
      },
      
      initializeGame() {
      this.canvas = this.$refs.gameCanvas;
      this.ctx = this.canvas.getContext('2d');
      this.canvas.focus();
      
      // Taxi in die Mitte des 400x400px Tiles positionieren
      this.taxi.x = 250 - this.taxi.width/2; // Zentriert in der Mitte
      this.taxi.y = 250 - this.taxi.height/2; // Zentriert in der Mitte
      this.taxi.angle = 0; // Fahrtrichtung nach oben
      this.taxi.speed = 0; // Geschwindigkeit zurücksetzen
      // Vorherige Position und Skip-Flag setzen, damit erste Prüfung nicht triggert
      this.prevTaxiX = this.taxi.x;
      this.prevTaxiY = this.taxi.y;
      this.skipRedLightOneFrame = true;
      
      // Aktuelle Tile-Position setzen
      this.currentTile.row = 0;
      this.currentTile.col = 0;
      
      this.generateLevel();
      this.initializePassengerGeneration();
      this.startGame();
    },
    
    updateCanvasSize() {
      // Canvas ist immer 400x400px für ein einzelnes Tile
      this.canvas.width = 500;
      this.canvas.height = 500;
    },
    
    updateCurrentTile() {
      // Wechsel auf benachbarte Tiles, sobald der Rand erreicht wird
      if (!this.currentMap || !this.currentMap.mapData) return;
      const mapData = this.currentMap.mapData;
      const rows = mapData.length;
      const cols = mapData[0] ? mapData[0].length : 0;
      const tileSize = this.tiles.size; // 500px
      
      let newRow = this.currentTile.row;
      let newCol = this.currentTile.col;
      let moved = false;
      let moveDir = null; // 'right'|'left'|'down'|'up'
      
      // Nach rechts: sobald rechte Kante den Rand erreicht (>=)
      if (this.taxi.x + this.taxi.width >= tileSize && newCol < cols - 1) {
        this.taxi.x = 0; // direkt an linken Rand des neuen Tiles
        newCol += 1;
        moved = true;
        moveDir = 'right';
      }
      // Nach links: sobald linke Kante den Rand erreicht (<= 0)
      if (!moved && this.taxi.x <= 0 && newCol > 0) {
        this.taxi.x = tileSize - this.taxi.width; // direkt an rechten Rand des neuen Tiles
        newCol -= 1;
        moved = true;
        moveDir = 'left';
      }
      // Nach unten: sobald untere Kante den Rand erreicht (>=)
      if (!moved && this.taxi.y + this.taxi.height >= tileSize && newRow < rows - 1) {
        this.taxi.y = 0; // direkt an oberen Rand des neuen Tiles
        newRow += 1;
        moved = true;
        moveDir = 'down';
      }
      // Nach oben: sobald obere Kante den Rand erreicht (<= 0)
      if (!moved && this.taxi.y <= 0 && newRow > 0) {
        this.taxi.y = tileSize - this.taxi.height; // direkt an unteren Rand des neuen Tiles
        newRow -= 1;
        moved = true;
        moveDir = 'up';
      }
      
      // Falls Wechsel möglich war, übernehme neue Tile-Position
      if (moved) {
        this.currentTile.row = newRow;
        this.currentTile.col = newCol;
        // Nach Tile-Wechsel: eine Frame lang Rotlicht-Prüfung aussetzen
        // und prev-Position auf aktuelle setzen, um false positives zu vermeiden
        this.prevTaxiX = this.taxi.x;
        this.prevTaxiY = this.taxi.y;
        this.skipRedLightOneFrame = true;
        // Radar mit 12% Wahrscheinlichkeit aktivieren beim Betreten des neuen Tiles
        this.activeRadar = Math.random() < 0.12;
        // Achse und Position festlegen: Messung findet bei 150px vom Rand statt
        if (this.activeRadar) {
          const size = this.tiles.size;
          if (moveDir === 'right') { this.radarAxis = 'V'; this.radarLinePos = 150; }
          else if (moveDir === 'left') { this.radarAxis = 'V'; this.radarLinePos = size - 150; }
          else if (moveDir === 'down') { this.radarAxis = 'H'; this.radarLinePos = 150; }
          else if (moveDir === 'up') { this.radarAxis = 'H'; this.radarLinePos = size - 150; }
          else { this.radarAxis = 'H'; this.radarLinePos = 150; }
        } else {
          this.radarAxis = null;
        }
        this.radarTicketShown = false;
        return;
      }
      
      // An den äußeren Grenzen der Map: innerhalb des aktuellen Tiles halten
      this.taxi.x = Math.max(0, Math.min(tileSize - this.taxi.width, this.taxi.x));
      this.taxi.y = Math.max(0, Math.min(tileSize - this.taxi.height, this.taxi.y));
    },
    
    setupEventListeners() {
      // Event-Listener auf Document registrieren (Canvas bleibt immer sichtbar)
      document.addEventListener('keydown', this.handleKeyDown);
      document.addEventListener('keyup', this.handleKeyUp);
    },
    setupAudioUnlockHandlers() {
      this.audioUnlockHandler = async () => {
        await this.initAudioOnUserInteraction();
        if (this.audioContext && this.audioContext.state === 'suspended') {
          await this.audioContext.resume().catch(() => {});
        }
        document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true });
        document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true });
        document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true });
        this.audioUnlockHandler = null;
      };
      document.addEventListener('pointerdown', this.audioUnlockHandler, { capture: true, passive: true });
      document.addEventListener('touchstart', this.audioUnlockHandler, { capture: true, passive: true });
      document.addEventListener('keydown', this.audioUnlockHandler, { capture: true });
    },
    
    async initializeMotorSound() {
      // AudioContext wird erst bei erster Benutzerinteraktion erstellt
    },

    async initAudioOnUserInteraction() {
      if (this.audioContext) return; // Bereits initialisiert
      
      try {
        // Übernehme ggf. global vorhandenen Context
        if (window.__TaxiAudioContext) {
          this.audioContext = window.__TaxiAudioContext;
        } else {
          this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
          window.__TaxiAudioContext = this.audioContext;
        }
        // Autoplay-Policy: Context sofort im User-Gesture fortsetzen
        if (this.audioContext.state === 'suspended') {
          await this.audioContext.resume();
        }
        if (!this.motorSound) {
          // Ggf. globalen Motor übernehmen
          if (window.__TaxiMotorSound) {
            this.motorSound = window.__TaxiMotorSound;
          } else {
            const generator = new NoiseGenerator();
            this.motorSound = new MotorSound(this.audioContext, generator);
            await this.motorSound.init();
            window.__TaxiMotorSound = this.motorSound;
          }
        }
        // Falls bereits Bewegung anliegt, Sound direkt starten
        const speedKmh = this.taxi.speed * 5;
        if (speedKmh > 0) {
          this.motorSound.start();
          // Sofort Parameter setzen
          const speedFactor = Math.min(speedKmh / 120, 1);
          const motorSpeed = 0.3 + (speedFactor * 0.3);
          const volume = 0.1 + (speedFactor * 0.4);
          this.motorSound.setSpeed(motorSpeed);
          this.motorSound.setVolume(volume);
        }
      } catch (error) {
        console.warn('[Audio] init: Fehler', error);
      }
    },

    updateMotorSound() {
      if (!this.motorSound) return;
      
      const speedKmh = this.taxi.speed * 5; // Geschwindigkeit in km/h
      const isMoving = speedKmh > 0;
      
      // Start NICHT hier (kein User-Gesture). Stop mit 1s Verzögerung, wenn still
      if (!isMoving) {
        if (this.motorStopTimeout == null && this.motorSound.isPlaying) {
          this.motorStopTimeout = setTimeout(() => {
            // Nur stoppen, wenn weiterhin keine Bewegung
            const still = (this.taxi.speed * 5) <= 0;
            if (still && this.motorSound && this.motorSound.isPlaying) {
              this.motorSound.stop();
            }
            this.motorStopTimeout = null;
          }, 500);
        }
      } else {
        // Bewegung: ggf. geplanten Stopp abbrechen
        if (this.motorStopTimeout) {
          clearTimeout(this.motorStopTimeout);
          this.motorStopTimeout = null;
        }
      }
      
      if (isMoving) {
        // Geschwindigkeitsabhängige Tonhöhe und Lautstärke
        const speedFactor = Math.min(speedKmh / 120, 1); // 0-1 basierend auf 0-120 km/h
        const motorSpeed = 0.3 + (speedFactor * 0.3); // 0.3 bis 1.5
        const volume = 0.1 + (speedFactor * 0.4); // 0.1 bis 0.5
        
        this.motorSound.setSpeed(motorSpeed);
        this.motorSound.setVolume(volume);
      }
    },

    cleanup() {
      console.log('🧹 Starting cleanup...');
      
      // Game Loop stoppen
      if (this.gameLoop) {
        cancelAnimationFrame(this.gameLoop);
        this.gameLoop = null;
      }
      
      // Motor Sound stoppen
      if (this.motorSound) { 
        this.motorSound.stop(); 
        this.motorSound = null;
      }
      
      // Alle Timeouts bereinigen
      if (this.motorStopTimeout) {
        clearTimeout(this.motorStopTimeout);
        this.motorStopTimeout = null;
      }
      if (this.passengerGenerationTimeout) {
        clearTimeout(this.passengerGenerationTimeout);
        this.passengerGenerationTimeout = null;
      }
      if (this.crashDialogTimeout) {
        clearTimeout(this.crashDialogTimeout);
        this.crashDialogTimeout = null;
      }
      if (this.memoryCleanupInterval) {
        clearInterval(this.memoryCleanupInterval);
        this.memoryCleanupInterval = null;
      }
      
      // Event-Listener von Document entfernen
      document.removeEventListener('keydown', this.handleKeyDown);
      document.removeEventListener('keyup', this.handleKeyUp);
      
      // Audio Unlock Handler bereinigen
      if (this.audioUnlockHandler) {
        document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true });
        document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true });
        document.removeEventListener('keydown', this.audioUnlockHandler, { capture: true });
        this.audioUnlockHandler = null;
      }
      
      // Listen und Sets bereinigen
      this.waitingPassengersList = [];
      this.loadedPassengersList = [];
      this.occupiedHouses.clear();
      this.cars = []; // Autos bereinigen
      
      // Canvas Context bereinigen
      if (this.ctx) {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx = null;
      }
      
      // Canvas Referenz bereinigen
      this.canvas = null;
      
      // Audio Context bereinigen (optional - kann global bleiben)
      // if (this.audioContext && this.audioContext.state !== 'closed') {
      //   this.audioContext.close();
      //   this.audioContext = null;
      // }
      
      // Maps und Objekte bereinigen
      this.trafficLightStates = {};
      this.keys = {};
      
      // Bilder und Assets bereinigen
      this.taxiImage = null;
      this.houseImage = null;
      this.passengerImages = {};
      this.carImage = null; // Auto-Bild bereinigen
      this.tiles = null;
      
      console.log('🧹 Cleanup completed');
    },
    
    // Regelmäßige Memory-Cleanup-Methode
    performMemoryCleanup() {
      console.log('🧹 Performing memory cleanup...');
      
      // Canvas NICHT leeren – das verursacht sichtbares Flackern / Grau
      // Wir verlassen uns auf das reguläre render() zum Überschreiben des Frames
      
      // Traffic Light States aggressiver bereinigen
      if (this.trafficLightStates && Object.keys(this.trafficLightStates).length > 20) {
        console.log('🧹 Cleaning up traffic light states');
        // Nur States für aktuelle Map behalten
        if (this.currentMap && this.currentMap.tiles) {
          const currentTileKeys = new Set();
          this.currentMap.tiles.forEach(tile => {
            if (tile.trafficLight || (tile.meta && tile.meta.trafficLight)) {
              currentTileKeys.add(`${tile.x},${tile.y}`);
            }
          });
          
          // Entferne States für nicht mehr existierende Tiles
          Object.keys(this.trafficLightStates).forEach(key => {
            if (!currentTileKeys.has(key)) {
              delete this.trafficLightStates[key];
            }
          });
        } else {
          // Fallback: komplett leeren wenn keine Map
          this.trafficLightStates = {};
        }
      }
      
      // Passagier-Listen aggressiver begrenzen
      if (this.waitingPassengersList && this.waitingPassengersList.length > 20) {
        console.log('🧹 Trimming waiting passengers list');
        this.waitingPassengersList = this.waitingPassengersList.slice(-10);
      }
      
      if (this.loadedPassengersList && this.loadedPassengersList.length > 20) {
        console.log('🧹 Trimming loaded passengers list');
        this.loadedPassengersList = this.loadedPassengersList.slice(-10);
      }
      
      // Autos-Liste NICHT hart trimmen, um keine abrupten Desyncs zu erzeugen
      
      // Keys-Objekt bereinigen (entferne nicht mehr gedrückte Tasten)
      if (this.keys) {
        const activeKeys = Object.keys(this.keys).filter(key => this.keys[key]);
        if (activeKeys.length === 0) {
          this.keys = {};
        }
      }
      
      // Force Garbage Collection (falls verfügbar)
      if (window.gc) {
        window.gc();
      }
      
      console.log('🧹 Memory cleanup completed');
    },
    
    generateLevel() {
      this.passengers = [];
      this.destinations = [];
      this.obstacles = [];
      
      // Passagiere/Ziele werden aktuell nicht generiert
      
      
      // Generiere Hindernisse (außerhalb der Straßen)
      for (let i = 0; i < 3; i++) {
        const position = this.getRandomOffRoadPosition();
        this.obstacles.push({
          x: position.x,
          y: position.y,
          width: 20,
          height: 20
        });
      }
    },
    
    initializePassengerGeneration() {
      // Setze zufälliges Intervall für nächste Passagier-Generierung (10-35 Sekunden)
      this.passengerGenerationInterval = Math.floor(Math.random() * 25000) + 10000; // 10-35 Sekunden
      this.lastPassengerGeneration = Date.now();
      this.occupiedHouses.clear();
      
      // Generiere sofort einen Passagier, falls die Map bereits geladen ist
      if (this.currentMap && Array.isArray(this.currentMap.tileHouses) && this.currentMap.tileHouses.length > 0) {
        this.generateWaitingPassenger();
      } else {
        // Falls die Map noch nicht geladen ist, versuche es in 2 Sekunden erneut
        this.passengerGenerationTimeout = setTimeout(() => {
          this.generateWaitingPassenger();
        }, 2000);
      }
    },

    generateWaitingPassenger() {
      if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses) || !Array.isArray(this.currentMap.tileStreets)) {
        // Versuche es in 2 Sekunden erneut
        this.passengerGenerationTimeout = setTimeout(() => {
          this.generateWaitingPassenger();
        }, 2000);
        return;
      }
      
      // 1. Sammle alle Straßen mit verfügbaren Häusern
      const streetsWithHouses = [];
      
      for (const street of this.currentMap.tileStreets) {
        // Finde alle Häuser auf diesem Straßen-Tile
        const housesOnThisTile = this.currentMap.tileHouses.filter(house => 
          house.x === street.x && house.y === street.y
        );
        
        if (housesOnThisTile.length > 0) {
          // Prüfe ob diese Straße gültige Straßennamen hat
          const hasValidStreetName = (street.streetNameH && street.streetNameH.name && street.streetNameH.name.trim() !== '') ||
                                   (street.streetNameV && street.streetNameV.name && street.streetNameV.name.trim() !== '');
          
          if (hasValidStreetName) {
            streetsWithHouses.push({
              street: street,
              houses: housesOnThisTile
            });
          }
        }
      }
      
      if (streetsWithHouses.length === 0) {
        console.log('Keine Straßen mit verfügbaren Häusern gefunden');
        return;
      }
      
      // 2. Wähle zufällige Straße mit Häusern
      const selectedStreetData = streetsWithHouses[Math.floor(Math.random() * streetsWithHouses.length)];
      const selectedStreet = selectedStreetData.street;
      const availableHouses = selectedStreetData.houses;
      
      // 3. Wähle zufälliges Haus auf dieser Straße
      const selectedHouse = availableHouses[Math.floor(Math.random() * availableHouses.length)];
      const houseIndex = this.currentMap.tileHouses.findIndex(h => h === selectedHouse);
      const houseId = `${selectedHouse.x}-${selectedHouse.y}-${houseIndex}`;
      
      // Prüfe ob das Haus bereits belegt ist
      if (this.occupiedHouses.has(houseId)) {
        // Haus bereits belegt, versuche es erneut
        this.generateWaitingPassenger();
        return;
      }
      
      // 4. Bestimme Straßenname basierend auf der Haus-Ecke
      let streetName = null;
      const corner = selectedHouse.corner;
      
      if (corner === 'lo' || corner === 'ro') {
        // Horizontale Straße
        if (selectedStreet.streetNameH && selectedStreet.streetNameH.name) {
          streetName = selectedStreet.streetNameH.name;
        }
      } else if (corner === 'lu' || corner === 'ru') {
        // Vertikale Straße
        if (selectedStreet.streetNameV && selectedStreet.streetNameV.name) {
          streetName = selectedStreet.streetNameV.name;
        }
      }
      
      // Fallback sollte nie auftreten, da wir nur Straßen mit gültigen Namen auswählen
      if (!streetName || streetName.trim() === '') {
        console.error('Fehler: Kein gültiger Straßenname gefunden für Haus:', selectedHouse);
        this.generateWaitingPassenger(); // Versuche es erneut
        return;
      }
      
      // 5. Finde die Hausnummer für dieses spezifische Haus
      const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.corner}`;
      const houseNumber = this.houseNumbers[houseKey] || 1;
      
      // 6. Generiere Namen und Geschlecht
      const nameData = this.generatePassengerName();
      
      // 7. Erstelle Passagier
      const passenger = {
        id: Date.now() + Math.random(), // Eindeutige ID
        name: nameData.fullName,
        gender: nameData.gender,
        location: `${streetName} ${houseNumber}`,
        houseId: houseId,
        tileX: selectedHouse.x,
        tileY: selectedHouse.y,
        houseIndex: houseIndex,
        houseCorner: selectedHouse.corner, // Speichere auch die Ecke
        imageIndex: this.generatePassengerImageIndex(nameData.gender), // Bildindex basierend auf Geschlecht
        createdAt: Date.now() // Zeitstempel der Erstellung
      };
      
      // 8. Füge Passagier zur Liste hinzu und markiere Haus als belegt
      this.waitingPassengersList.push(passenger);
      this.occupiedHouses.add(houseId);
    },

    generatePassengerName() {
      const maleNames = [
        'Max', 'Tom', 'Ben', 'Lukas', 'Paul', 'Felix', 'Jonas', 'Tim', 'Noah', 'Finn',
        'Leon', 'Liam', 'Elias', 'Henry', 'Anton', 'Theo', 'Emil', 'Oskar', 'Mats', 'Luis',
        'Alexander', 'Sebastian', 'David', 'Daniel', 'Michael', 'Christian', 'Andreas', 'Stefan',
        'Markus', 'Thomas', 'Matthias', 'Martin', 'Peter', 'Klaus', 'Wolfgang', 'Jürgen',
        'Rainer', 'Uwe', 'Dieter', 'Hans', 'Günter', 'Karl', 'Walter', 'Helmut', 'Manfred',
        'Jens', 'Sven', 'Tobias', 'Nils', 'Jan', 'Marcel', 'Kevin', 'Dennis', 'Marco',
        'Philipp', 'Simon', 'Florian', 'Dominik', 'Patrick', 'Oliver', 'Timo', 'Björn'
      ];
      const femaleNames = [
        'Anna', 'Lisa', 'Sarah', 'Emma', 'Julia', 'Marie', 'Sophie', 'Laura', 'Mia', 'Emma',
        'Lina', 'Mila', 'Hannah', 'Lea', 'Emilia', 'Clara', 'Leni', 'Marlene', 'Frieda', 'Ida',
        'Katharina', 'Christina', 'Nicole', 'Stefanie', 'Jennifer', 'Jessica', 'Melanie', 'Sandra',
        'Andrea', 'Petra', 'Monika', 'Ursula', 'Brigitte', 'Ingrid', 'Gisela', 'Elisabeth',
        'Maria', 'Barbara', 'Helga', 'Gertrud', 'Irmgard', 'Hildegard', 'Waltraud', 'Renate',
        'Sabine', 'Kerstin', 'Anja', 'Tanja', 'Nadine', 'Katrin', 'Silke', 'Birgit',
        'Susanne', 'Martina', 'Karin', 'Heike', 'Doris', 'Elke', 'Bärbel', 'Jutta',
        'Gabriele', 'Angelika', 'Christine', 'Annette', 'Beate', 'Cornelia', 'Diana', 'Eva'
      ];
      const lastNames = [
        'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Weber', 'Meyer', 'Wagner', 'Becker',
        'Schulz', 'Hoffmann', 'Schäfer', 'Koch', 'Bauer', 'Richter', 'Klein', 'Wolf',
        'Schröder', 'Neumann', 'Schwarz', 'Zimmermann', 'Braun', 'Hofmann', 'Lange', 'Schmitt',
        'Werner', 'Schmitz', 'Krause', 'Meier', 'Lehmann', 'Köhler', 'Huber', 'Kaiser',
        'Fuchs', 'Peters', 'Lang', 'Scholz', 'Möller', 'Weiß', 'Jung', 'Hahn', 'Schubert',
        'Schuster', 'Winkler', 'Berger', 'Roth', 'Beck', 'Lorenz', 'Baumann', 'Franke',
        'Albrecht', 'Ludwig', 'Winter', 'Simon', 'Kraus', 'Vogt', 'Stein', 'Jäger',
        'Otto', 'Sommer', 'Groß', 'Seidel', 'Heinrich', 'Brandt', 'Haas', 'Schreiber',
        'Graf', 'Schulte', 'Dietrich', 'Ziegler', 'Kuhn', 'Pohl', 'Engel', 'Horn'
      ];
      
      // Wähle zufällig zwischen männlich und weiblich
      const isMale = Math.random() < 0.5;
      const firstNames = isMale ? maleNames : femaleNames;
      const firstName = firstNames[Math.floor(Math.random() * firstNames.length)];
      const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
      
      return {
        fullName: `${firstName} ${lastName}`,
        gender: isMale ? 'male' : 'female'
      };
    },

    generatePassengerImageIndex(gender) {
      // Bildindex basierend auf Geschlecht (1-3 männlich, 4-6 weiblich)
      if (gender === 'male') {
        return Math.floor(Math.random() * 3) + 1; // 1-3
      } else {
        return Math.floor(Math.random() * 3) + 4; // 4-6
      }
    },

    getTilesWithHouses() {
      // Erstelle Liste aller Tiles, die Häuser haben
      const tilesWithHouses = [];
      const houses = this.currentMap.tileHouses || [];
      
      // Sammle alle eindeutigen Tile-Koordinaten
      const tileSet = new Set();
      houses.forEach(house => {
        const tileKey = `${house.x}-${house.y}`;
        if (!tileSet.has(tileKey)) {
          tileSet.add(tileKey);
          tilesWithHouses.push({ x: house.x, y: house.y });
        }
      });
      
      return tilesWithHouses;
    },

    calculateShortestPath(startX, startY, endX, endY) {
      // Berechne Manhattan-Distanz (kürzester Weg in einem Raster)
      const distance = Math.abs(endX - startX) + Math.abs(endY - startY);
      
      // Wenn Start und Ziel identisch sind, mindestens 1 Tile Distanz
      // (da das Taxi das Tile verlassen und wieder betreten muss)
      if (distance === 0) {
        return 1;
      }
      
      // Für unterschiedliche Tiles: Distanz + 1 (Start-Tile wird mitgezählt)
      return distance + 1;
    },

    calculateBonusAndTime(startX, startY, endX, endY) {
      const shortestPath = this.calculateShortestPath(startX, startY, endX, endY);
      const bonus = shortestPath * this.bonusMultiplier;
      const maxTime = shortestPath * this.timePerTile;
      
      console.log(`Wegstrecke berechnet: Start(${startX},${startY}) -> Ziel(${endX},${endY}) = ${shortestPath} Tiles, Bonus: ${bonus}, Zeit: ${maxTime}s`);
      
      return {
        shortestPath,
        bonus,
        maxTime
      };
    },

    generatePassengerDestination() {
      // Generiere ein zufälliges Ziel für den Passagier
      if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets) || !Array.isArray(this.currentMap.tileHouses)) {
        return null;
      }
      
      // Finde alle Straßen-Tiles auf der Karte
      const streets = this.currentMap.tileStreets || [];
      const houses = this.currentMap.tileHouses || [];
      
      // Sammle alle gültigen Ziele vor der zufälligen Auswahl
      const validDestinations = [];
      
       for (const street of streets) {
         // Prüfe ob diese Straße Häuser hat
         const housesOnThisTile = houses.filter(house => house.x === street.x && house.y === street.y);
         
         if (housesOnThisTile.length > 0) {
           for (const house of housesOnThisTile) {
             const corner = house.corner;
             let streetName = null;
             
             if (corner === 'lo' || corner === 'ro') {
               // Horizontale Straße
               if (street.streetNameH && street.streetNameH.name) {
                 streetName = street.streetNameH.name;
               }
             } else if (corner === 'lu' || corner === 'ru') {
               // Vertikale Straße
               if (street.streetNameV && street.streetNameV.name) {
                 streetName = street.streetNameV.name;
               }
             }
             
             // Nur gültige Straßen hinzufügen
             if (streetName && streetName.trim() !== '') {
               // Hole Hausnummern nur für dieses spezifische Tile
               const key = `${street.x},${street.y},${house.corner}`;
               const houseNumber = this.houseNumbers[key];
               
               if (houseNumber != null) {
                 validDestinations.push({
                   streetName,
                   houseNumber,
                   location: `${streetName} ${houseNumber}`,
                   tileX: street.x,
                   tileY: street.y,
                   houseCorner: house.corner
                 });
               }
             }
           }
         }
       }
      
      
      if (validDestinations.length > 0) {
        // Wähle zufälliges gültiges Ziel
        const selectedDestination = validDestinations[Math.floor(Math.random() * validDestinations.length)];
        return selectedDestination;
      }
      
      return null;
    },

    updatePassengerGeneration() {
      const now = Date.now();
      const timeSinceLastGeneration = now - this.lastPassengerGeneration;
      
      if (timeSinceLastGeneration >= this.passengerGenerationInterval) {
        this.generateWaitingPassenger();
        this.lastPassengerGeneration = now;
        // Setze nächstes Intervall (10-35 Sekunden)
        this.passengerGenerationInterval = Math.floor(Math.random() * 25000) + 10000;
      }
    },

    updatePassengerTimers() {
      // Aktualisiere die verbleibende Zeit für alle geladenen Passagiere
      const now = Date.now();
      
      this.loadedPassengersList.forEach(passenger => {
        if (passenger.pickedUpAt && passenger.bonusData) {
          // Aktiviere Timer nach 2 Sekunden
          if (!passenger.timerActive) {
            const timeSincePickup = now - passenger.pickedUpAt;
            if (timeSincePickup >= 2000) {
              passenger.timerActive = true;
              passenger.lastUpdateTime = now;
            }
            return; // Timer noch nicht aktiv
          }
          
          // Throttle: Nur alle 100ms aktualisieren
          if (passenger.lastUpdateTime && (now - passenger.lastUpdateTime) < 100) {
            return;
          }
          
          const timeSincePickup = now - passenger.pickedUpAt;
          const elapsedTime = Math.floor(timeSincePickup / 1000);
          // Berechne die verbleibende Zeit, aber lasse sie nicht unter 0 fallen
          const remainingTime = passenger.bonusData.maxTime - elapsedTime;
          passenger.timeLeft = Math.max(0, remainingTime);
          passenger.lastUpdateTime = now;
        }
      });
    },

    // Prüft, ob das Taxi im Tank-Bereich ist und stillsteht
    checkRefuelingConditions() {
      const tileType = this.getCurrentTileType();
      
      // Nur auf Fuel-Tiles prüfen
      if (tileType !== 'fuelhorizontal' && tileType !== 'fuelvertical') {
        return false;
      }
      
      // Taxi muss stillstehen (Geschwindigkeit = 0)
      if (this.taxi.speed !== 0) {
        return false;
      }
      
      // Prüfe Position im Tank-Bereich (absolute Koordinaten)
      if (tileType === 'fuelhorizontal') {
        // Y-Bereich zwischen 0.195 und 0.299 (relativ) = 97.5px und 149.5px (absolut)
        const minY = 0.195 * 500; // 97.5px
        const maxY = 0.299 * 500; // 149.5px
        return this.taxi.y >= minY && this.taxi.y <= maxY;
      } else if (tileType === 'fuelvertical') {
        // X-Bereich zwischen 0.701 und 0.805 (relativ) = 350.5px und 402.5px (absolut)
        const minX = 0.701 * 500; // 350.5px
        const maxX = 0.805 * 500; // 402.5px
        return this.taxi.x >= minX && this.taxi.x <= maxX;
      }
      
      return false;
    },

    // Startet das Tanken
    startRefueling() {
      if (this.isRefueling || this.fuel >= 100) {
        return; // Bereits am Tanken oder Tank voll
      }
      
      this.isRefueling = true;
      
      // Tanken alle 0.1 Sekunden (100ms)
      this.refuelTimer = setInterval(() => {
        if (!this.checkRefuelingConditions()) {
          this.stopRefueling();
          return;
        }
        
        // Tanke 0.1% pro 0.1 Sekunden
        this.fuel = Math.min(100, this.fuel + 0.1);
        
        // Stoppe wenn Tank voll
        if (this.fuel >= 100) {
          this.stopRefueling();
        }
      }, 100);
    },

    // Stoppt das Tanken
    stopRefueling() {
      if (this.refuelTimer) {
        clearInterval(this.refuelTimer);
        this.refuelTimer = null;
      }
      this.isRefueling = false;
    },

    // Behandelt leeren Tank - Crash und Tank auffüllen
    handleEmptyTank() {
      // Stoppe das Tanken falls aktiv
      this.stopRefueling();
      
      // Crash durch leeren Tank
      this.countCrash('emptytank');
     
      // Zeige Crash-Dialog
      this.showCrashDialog('Leerer Tank! Der Tank wurde automatisch aufgefüllt.');
    },

    removePassengerFromWaitingList() {
      if (this.waitingPassengersList.length > 0) {
        // Entferne den ersten Passagier aus der Liste
        const removedPassenger = this.waitingPassengersList.shift();
        if (removedPassenger && removedPassenger.houseId) {
          // Gib das Haus wieder frei
          this.occupiedHouses.delete(removedPassenger.houseId);
        }
      }
    },

    removeExpiredPassengers() {
      const now = Date.now();
      const maxAge = 30000; // 30 Sekunden in Millisekunden
      
      // Filtere abgelaufene Passagiere heraus
      const expiredPassengers = [];
      this.waitingPassengersList = this.waitingPassengersList.filter(passenger => {
        const age = now - passenger.createdAt;
        if (age > maxAge) {
          expiredPassengers.push(passenger);
          // Gib das Haus wieder frei
          if (passenger.houseId) {
            this.occupiedHouses.delete(passenger.houseId);
          }
          return false; // Entferne aus der Liste
        }
        return true; // Behalte in der Liste
      });
      
      // Passagiere wurden entfernt (abgelaufen)
    },

    // Autos-Generierung mit 20% Wahrscheinlichkeit pro Sekunde
    updateCarGeneration() {
      const now = Date.now();
      
      // Prüfe alle Sekunde
      if (now - this.lastCarGeneration < this.carGenerationInterval) {
        return;
      }
      
      this.lastCarGeneration = now;
      
      // 20% Wahrscheinlichkeit pro Sekunde
      if (!this.isPaused && Math.random() < this.carSpawnProbability) {
        this.spawnCar(now);
      }
    },

    // Spawne ein neues Auto
    spawnCar(now = Date.now()) {
      // Begrenze die Anzahl der Autos
      if (this.cars.length >= 8) {
        return;
      }

      // Spawne Auto auf einer befahrbaren Position
      const spawnPosition = this.getRandomCarSpawnPosition();
      if (!spawnPosition) {
        return; // Keine gültige Spawn-Position gefunden
      }
      
      const car = {
        id: Date.now() + Math.random(), // Eindeutige ID
        x: spawnPosition.x,
        y: spawnPosition.y,
        angle: spawnPosition.angle,
        speed: 0.3 + Math.random() * 0.7, // Langsamere Geschwindigkeit zwischen 0.3-1.0
        width: 60, // Etwas breiter als Taxi
        height: 50, // Gleiche Höhe wie Taxi
        createdAt: now,
        color: this.getRandomCarColor(),
        isCar: true,
        currentTile: { row: 0, col: 0 }, // Aktuelle Tile-Position
        direction: spawnPosition.direction, // Fahrtrichtung
        lastPosition: { x: spawnPosition.x, y: spawnPosition.y }, // Letzte Position für Bewegung
        hasTurnedAtIntersection: false, // Flag um mehrfaches Abbiegen zu verhindern
        targetDirection: null, // Zielrichtung an der Kreuzung (wird beim Tile-Betreten festgelegt)
        targetLane: null // Zielspur basierend auf Zielrichtung
      };

      this.cars.push(car);
    },

    // Ermittelt erlaubte Spawn-Richtungen basierend auf dem aktuellen Tile-Typ
    getAllowedSpawnDirections() {
      const tileType = this.getCurrentTileType();
      
      // Basierend auf der allowedDirections Logik aus AdminTaxiToolsView.vue
      switch (tileType) {
        case 'cornertopleft': 
          return ['left', 'up']; // Kann von links und oben spawnen
        case 'cornertopright': 
          return ['right', 'up']; // Kann von rechts und oben spawnen
        case 'cornerbottomleft': 
          return ['left', 'down']; // Kann von links und unten spawnen
        case 'cornerbottomright': 
          return ['right', 'down']; // Kann von rechts und unten spawnen
        case 'horizontal':
        case 'fuelhorizontal': 
          return ['left', 'right']; // Kann von links und rechts spawnen
        case 'vertical':
        case 'fuelvertical': 
          return ['up', 'down']; // Kann von oben und unten spawnen
        case 'cross': 
          return ['left', 'right', 'up', 'down']; // Kann von allen Seiten spawnen
        case 'tup': 
          return ['up', 'left', 'right']; // Kann von oben, links und rechts spawnen
        case 'tdown': 
          return ['down', 'left', 'right']; // Kann von unten, links und rechts spawnen
        case 'tleft': 
          return ['left', 'up', 'down']; // Kann von links, oben und unten spawnen
        case 'tright': 
          return ['right', 'up', 'down']; // Kann von rechts, oben und unten spawnen
        default: 
          return []; // Keine erlaubten Spawn-Richtungen
      }
    },

    // Finde eine zufällige befahrbare Spawn-Position für ein Auto
    getRandomCarSpawnPosition() {
      if (!this.currentMap || !this.currentMap.tiles) {
        return null;
      }

      // Erlaubte Spawn-Richtungen für den aktuellen Tile-Typ ermitteln
      const allowedDirections = this.getAllowedSpawnDirections();
      
      if (allowedDirections.length === 0) {
        return null; // Keine erlaubten Spawn-Richtungen
      }
      
      // Zufällig eine erlaubte Richtung auswählen
      const spawnDirection = allowedDirections[Math.floor(Math.random() * allowedDirections.length)];
      
      // Spawn-Position basierend auf der Richtung berechnen
      const tileSize = this.tiles.size;
      let relativeX, relativeY, angle;
      
      switch (spawnDirection) {
        case 'right':
          // Von links nach rechts fahren, auf der rechten Straßenseite (y=0.55-0.625)
          relativeX = 0;
          relativeY = 0.55 + Math.random() * 0.075;
          angle = 0;
          break;
        case 'left':
          // Von rechts nach links fahren, auf der linken Straßenseite (y=0.375-0.45)
          relativeX = 1;
          relativeY = 0.375 + Math.random() * 0.075;
          angle = Math.PI;
          break;
        case 'down':
          // Von oben nach unten fahren, auf der linken Straßenseite (x=0.375-0.45)
          relativeX = 0.375 + Math.random() * 0.075;
          relativeY = 0;
          angle = Math.PI / 2;
          break;
        case 'up':
          // Von unten nach oben fahren, auf der rechten Straßenseite (x=0.55-0.625)
          // Direkt im befahrbaren Bereich starten (relY ≈ 0.78)
          relativeX = 0.55 + Math.random() * 0.075;
          relativeY = 0.78;
          angle = -Math.PI / 2;
          break;
        default:
          return null;
      }
      
      const x = relativeX * tileSize;
      const y = relativeY * tileSize;
      
      // Debug-Ausgabe für tile-basierte Spawn-Logik
      const tileType = this.getCurrentTileType();
      console.log(`[Cars][spawn] Tile: ${tileType}, Direction: ${spawnDirection}, Pos: (${relativeX.toFixed(2)}, ${relativeY.toFixed(2)})`);
      
      return {
        x: Math.round(x), 
        y: Math.round(y),
        angle: angle, 
        direction: spawnDirection
      };
    }
    },

    // Prüfe ob eine Position befahrbar ist
    isPositionDriveable(x, y) {
      const tileType = this.getCurrentTileType();
      const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
      
      // Konvertiere absolute Koordinaten zu relativen (0-1)
      const relativeX = x / this.tiles.size;
      const relativeY = y / this.tiles.size;
      
      return streetCoordinates.isPointDriveable(relativeX, relativeY, streetTileType, 1);
    },

    // Fahrzeuge (KI) dürfen in den Screen hineingleiten: Ränder als befahrbar behandeln
    isPositionDriveableForCars(x, y, direction) {
      // Toleranz: 1px über/unter dem Canvasrand als befahrbar werten
      if (x < 0 || y < 0 || x > this.tiles.size || y > this.tiles.size) {
        // Wenn wir am Rand nach innen fahren, erlauben
        // up: y nimmt ab; down: y nimmt zu; right: x nimmt zu; left: x nimmt ab
        if (direction === 'up' && y >= -1) return true;
        if (direction === 'down' && y <= this.tiles.size + 1) return true;
        if (direction === 'right' && x <= this.tiles.size + 1) return true;
        if (direction === 'left' && x >= -1) return true;
        // weit außerhalb: nicht befahrbar
        return false;
      }
      // Innerhalb des Tiles: normale Prüfung
      return this.isPositionDriveable(x, y);
    },


    // Zufällige Auto-Farbe
    getRandomCarColor() {
      const colors = [
        '#FF0000', // Rot
        '#0000FF', // Blau
        '#00FF00', // Grün
        '#FFFF00', // Gelb
        '#FF00FF', // Magenta
        '#00FFFF', // Cyan
        '#FFFFFF', // Weiß
        '#000000'  // Schwarz
      ];
      return colors[Math.floor(Math.random() * colors.length)];
    },

    // Aktualisiere alle Autos
    updateCars() {
      const now = Date.now();
      const maxAge = 45000; // 45 Sekunden

      this.cars = this.cars.filter(car => {
        // Entferne alte Autos
        if (now - car.createdAt > maxAge) {
          return false;
        }

        // Bewege das Auto nur wenn es auf einer befahrbaren Position ist
        this.updateCarMovement(car);

        // Entferne Autos, die außerhalb des Bildschirms sind
        if (car.x < -50 || car.x > 550 || car.y < -50 || car.y > 550) {
          return false;
        }

        return true;
      });
    },

    // Aktualisiere die Bewegung eines einzelnen Autos
    updateCarMovement(car) {
      // Speichere aktuelle Position
      car.lastPosition = { x: car.x, y: car.y };
      
      // Setze Zielrichtung beim ersten Betreten des Tiles
      if (!car.targetDirection) {
        car.targetDirection = this.getRandomTargetDirection(car.direction);
        car.targetLane = this.getTargetLane(car.targetDirection);
      }
      
      // Prüfe ob Auto an einer Kreuzung ist und zur Zielrichtung abbiegen soll
      if (this.shouldCarTurnAtIntersection(car) && !car.hasTurnedAtIntersection) {
        this.turnCarToTarget(car);
        car.hasTurnedAtIntersection = true;
      }
      
      // Berechne neue Position
      const newX = car.x + Math.cos(car.angle) * car.speed;
      const newY = car.y + Math.sin(car.angle) * car.speed;
      
      // Prüfe ob neue Position befahrbar ist (Autos dürfen an Rändern hinein gleiten)
      if (this.isPositionDriveableForCars(newX, newY, car.direction)) {
        // Position ist befahrbar - bewege Auto
        car.x = newX;
        car.y = newY;
      } else {
        // Vorzugsweise zur geplanten Zielrichtung abbiegen (z. B. Kurve unten->rechts)
        if (car.targetDirection) {
          const prevAngle = car.angle;
          const prevDir = car.direction;
          this.turnCarToTarget(car);
          const tryX = car.x + Math.cos(car.angle) * car.speed;
          const tryY = car.y + Math.sin(car.angle) * car.speed;
          if (this.isPositionDriveableForCars(tryX, tryY, car.direction)) {
            car.x = tryX;
            car.y = tryY;
          } else {
            // Rückgängig machen, falls Zielrichtung auch nicht geht
            car.angle = prevAngle;
            car.direction = prevDir;
            // Fallback: alternative Richtung suchen (ohne zurückzufahren)
            this.adjustCarDirection(car);
          }
        } else {
          // Position ist nicht befahrbar - suche alternative Richtung (ohne zurückzufahren)
          this.adjustCarDirection(car);
        }
      }
    },

    // Bestimme zufällige Zielrichtung basierend auf Tile-Art und aktueller Richtung
    getRandomTargetDirection(currentDirection) {
      const tileType = this.getCurrentTileType();
      const possibleDirections = [];
      
      // Bestimme mögliche Richtungen basierend auf Tile-Typ
      switch (tileType) {
        case 'cornerTopLeft':
          // L-förmige Kurve: oben-links
          if (currentDirection === 'right') possibleDirections.push('up');
          else if (currentDirection === 'down') possibleDirections.push('left');
          break;
          
        case 'cornerTopRight':
          // L-förmige Kurve: oben-rechts
          if (currentDirection === 'left') possibleDirections.push('up');
          else if (currentDirection === 'down') possibleDirections.push('right');
          break;
          
        case 'cornerBottomLeft':
          // L-förmige Kurve: unten-links
          if (currentDirection === 'right') possibleDirections.push('down');
          else if (currentDirection === 'up') possibleDirections.push('left');
          break;
          
        case 'cornerBottomRight':
          // L-förmige Kurve: unten-rechts
          if (currentDirection === 'left') possibleDirections.push('down');
          else if (currentDirection === 'up') possibleDirections.push('right');
          break;
          
        case 'crossing':
          // Kreuzung: alle Richtungen außer zurück
          switch (currentDirection) {
            case 'right': possibleDirections.push('up', 'down'); break;
            case 'left': possibleDirections.push('up', 'down'); break;
            case 'up': possibleDirections.push('left', 'right'); break;
            case 'down': possibleDirections.push('left', 'right'); break;
          }
          break;
          
        default:
          // Für andere Tile-Typen: nur geradeaus
          possibleDirections.push(currentDirection);
          break;
      }
      
      // Fallback: wenn keine Richtungen gefunden, geradeaus
      if (possibleDirections.length === 0) {
        possibleDirections.push(currentDirection);
      }
      
      return possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
    },

    // Bestimme Zielspur basierend auf Zielrichtung
    getTargetLane(targetDirection) {
      switch (targetDirection) {
        case 'left':
          return { y: 0.375 + Math.random() * 0.125 }; // y=0.375-0.5
        case 'right':
          return { y: 0.5 + Math.random() * 0.125 }; // y=0.5-0.625
        case 'down':
          return { x: 0.375 + Math.random() * 0.125 }; // x=0.375-0.5
        case 'up':
          return { x: 0.5 + Math.random() * 0.125 }; // x=0.5-0.625
      }
    },

    // Prüfe, ob am aktuellen Tile der Abbiegepunkt erreicht ist
    shouldCarTurnAtIntersection(car) {
      const rx = car.x / this.tiles.size;
      const ry = car.y / this.tiles.size;
      const tileType = this.mapTileTypeToStreetCoordinates(this.getCurrentTileType());
      // Engeres Toleranzfenster: 0.495..0.505
      const nearYCenter = ry > 0.495 && ry < 0.505; // vertikale Mitte erreicht
      const nearXCenter = rx > 0.495 && rx < 0.505; // horizontale Mitte erreicht

      switch (tileType) {
        case 'cornerBottomRight':
          // Straße: unten -> rechts. Von unten kommend (up) am Y-Zentrum nach rechts abbiegen.
          if (car.direction === 'up') return nearYCenter;
          // Von links kommend (left) am X-Zentrum nach unten abbiegen.
          if (car.direction === 'left') return nearXCenter;
          return false;
        case 'cornerBottomLeft':
          // Straße: unten -> links
          if (car.direction === 'up') return nearYCenter; // dann links abbiegen
          if (car.direction === 'right') return nearXCenter; // dann runter abbiegen
          return false;
        case 'cornerTopRight':
          // Straße: oben -> rechts
          if (car.direction === 'down') return nearYCenter; // dann rechts
          if (car.direction === 'left') return nearXCenter; // dann hoch
          return false;
        case 'cornerTopLeft':
          // Straße: oben -> links
          if (car.direction === 'down') return nearYCenter; // dann links
          if (car.direction === 'right') return nearXCenter; // dann hoch
          return false;
        default:
          // Für Kreuzungen etc. beide Zentren oder explizit angeforderte Zielrichtung
          return nearXCenter && nearYCenter;
      }
    },

    // Auto zur Zielrichtung abbiegen
    turnCarToTarget(car) {
      const targetDirection = car.targetDirection;
      
      switch (targetDirection) {
        case 'left':
          car.angle = Math.PI;
          car.direction = 'left';
          break;
        case 'right':
          car.angle = 0;
          car.direction = 'right';
          break;
        case 'down':
          car.angle = Math.PI / 2;
          car.direction = 'down';
          break;
        case 'up':
          car.angle = -Math.PI / 2;
          car.direction = 'up';
          break;
      }
    },


    // Passe die Richtung eines Autos an, wenn es nicht weiterfahren kann
    adjustCarDirection(car) {
      const directions = [
        { angle: 0, name: 'right' },
        { angle: Math.PI, name: 'left' },
        { angle: Math.PI / 2, name: 'down' },
        { angle: -Math.PI / 2, name: 'up' }
      ];
      
      // Gegengerichtete Richtung (zurückfahren) ausschließen
      const opposite = {
        right: 'left',
        left: 'right',
        up: 'down',
        down: 'up'
      }[car.direction];
      
      // Versuche verschiedene Richtungen, zuerst die geplante Zielrichtung, dann andere (ohne zurück)
      const preferredOrder = [];
      if (car.targetDirection && car.targetDirection !== opposite) preferredOrder.push(car.targetDirection);
      directions.forEach(d => {
        if (d.name !== opposite && d.name !== (preferredOrder[0] || '')) preferredOrder.push(d.name);
      });

      for (let i = 0; i < preferredOrder.length; i++) {
        const name = preferredOrder[i];
        const angle = name === 'right' ? 0 : (name === 'left' ? Math.PI : (name === 'down' ? Math.PI/2 : -Math.PI/2));
        const testX = car.x + Math.cos(angle) * car.speed;
        const testY = car.y + Math.sin(angle) * car.speed;
        if (this.isPositionDriveableForCars(testX, testY, name)) {
          car.angle = angle;
          car.direction = name;
          return;
        }
      }
      
      // Keine befahrbare Richtung gefunden - bewege zurück zur letzten Position
      car.x = car.lastPosition.x;
      car.y = car.lastPosition.y;
    },

    getPassengerTimeLeft(passenger) {
      const now = Date.now();
      const age = now - passenger.createdAt;
      const maxAge = 30000; // 30 Sekunden
      const timeLeft = Math.max(0, Math.ceil((maxAge - age) / 1000));
      return timeLeft;
    },

    getHouseNumbersForStreet(streetName) {
      const houseNumbers = [];
      
      if (!this.currentMap || !this.currentMap.tileStreets || !this.houseNumbers) {
        return houseNumbers;
      }
      
      // Finde alle Tiles mit dieser Straße
      const tilesWithStreet = this.currentMap.tileStreets.filter(ts => 
        (ts.streetNameH && ts.streetNameH.name === streetName) ||
        (ts.streetNameV && ts.streetNameV.name === streetName)
      );
      
      // Sammle nur Hausnummern, die tatsächlich zu dieser Straße gehören
      tilesWithStreet.forEach(ts => {
        const isHorizontal = ts.streetNameH && ts.streetNameH.name === streetName;
        const isVertical = ts.streetNameV && ts.streetNameV.name === streetName;
        
        // Prüfe nur die Ecken, die zu dieser Straße gehören
        const relevantCorners = [];
        
        if (isHorizontal) {
          // Für horizontale Straßen: linke und rechte Ecken
          relevantCorners.push('lo', 'ro'); // left-outer, right-outer
        }
        
        if (isVertical) {
          // Für vertikale Straßen: obere und untere Ecken
          relevantCorners.push('lu', 'ru'); // left-upper, right-upper
        }
        
        // Sammle Hausnummern nur von den relevanten Ecken
        relevantCorners.forEach(corner => {
          const key = `${ts.x},${ts.y},${corner}`;
          const houseNumber = this.houseNumbers[key];
          if (houseNumber != null && !houseNumbers.includes(houseNumber)) {
            houseNumbers.push(houseNumber);
          }
        });
      });
      
      return houseNumbers.sort((a, b) => a - b); // Sortiere aufsteigend
    },
    
    getRandomRoadPosition() {
      const tileSize = this.tiles.size;
      const cols = 8;
      const rows = 8;
      
      let attempts = 0;
      while (attempts < 100) {
        const tileCol = Math.floor(Math.random() * cols);
        const tileRow = Math.floor(Math.random() * rows);
        const tileType = this.getTileType(tileRow, tileCol, rows, cols);
        
        // Zufällige Position innerhalb des Tiles
        const relativeX = Math.random();
        const relativeY = Math.random();
        
        const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
        if (streetCoordinates.isPointDriveable(relativeX, relativeY, streetTileType, 1)) {
          return {
            x: tileCol * tileSize + relativeX * tileSize,
            y: tileRow * tileSize + relativeY * tileSize
          };
        }
        attempts++;
      }
      
      // Fallback: Mitte des Spielfelds
      return { x: 250, y: 250 };
    },
    
    getRandomOffRoadPosition() {
      const tileSize = this.tiles.size;
      const cols = 8;
      const rows = 8;
      
      let attempts = 0;
      while (attempts < 100) {
        const tileCol = Math.floor(Math.random() * cols);
        const tileRow = Math.floor(Math.random() * rows);
        const tileType = this.getTileType(tileRow, tileCol, rows, cols);
        
        // Zufällige Position innerhalb des Tiles
        const relativeX = Math.random();
        const relativeY = Math.random();
        
        const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
        if (!streetCoordinates.isPointDriveable(relativeX, relativeY, streetTileType, 1)) {
          return {
            x: tileCol * tileSize + relativeX * tileSize,
            y: tileRow * tileSize + relativeY * tileSize
          };
        }
        attempts++;
      }
      
      // Fallback: Ecke des Spielfelds
      return { x: 20, y: 20 };
    },
    
    startGame() {
      this.gameRunning = true;
      // Setze Spielstart-Zeit
      if (!this.gameStartTime) {
        this.gameStartTime = Date.now();
      }
      // Stoppe bestehende Game-Loop falls vorhanden
      if (this.gameLoop) {
        cancelAnimationFrame(this.gameLoop);
      }
      this.gameLoop = requestAnimationFrame(this.update);
    },
    
    update() {
      if (!this.gameRunning || this.isPaused) {
        // Game Loop komplett stoppen wenn pausiert - kein unnötiges requestAnimationFrame
        return;
      }
      
      // Ampelschaltung tick
      this.updateTrafficLights();
      
      // Passagier-Generierung prüfen
      this.updatePassengerGeneration();
      
      // Autos-Generierung prüfen
      this.updateCarGeneration();
      
      // Autos aktualisieren
      this.updateCars();
      
      // Abgelaufene Passagiere entfernen
      this.removeExpiredPassengers();

      this.updateTaxi();
      this.handlePassengerActions();
      
      // Timer für geladene Passagiere aktualisieren (nach Passagier-Aktionen) - gedrosselt
      const nowTs = Date.now();
      if (nowTs - this.lastPassengerTimerUpdate >= 200) { // Alle 200ms statt jeden Frame
        this.updatePassengerTimers();
        this.lastPassengerTimerUpdate = nowTs;
      }
      
      this.checkCollisions();
      this.render();
      
      // Radar-Messung prüfen (falls aktiv)
      this.checkRadarMeasurement();

      // Minimap zeichnen (gedrosselt)
      if (nowTs - this.lastMinimapDraw >= this.minimapDrawInterval) {
        this.drawMinimap();
        this.lastMinimapDraw = nowTs;
      }
      
      this.gameLoop = requestAnimationFrame(this.update);
    },
    
    updateTaxi() {
      // Prüfe ob das Spiel pausiert ist
      if (this.isPaused) {
        return;
      }
      
      const currentTime = Date.now();
      
      // Prüfe ob genug Zeit vergangen ist für Geschwindigkeitsänderung
      if (currentTime - this.lastSpeedChange >= this.speedChangeInterval) {
        // Beschleunigung bei gedrücktem Pfeil nach oben (5er-Schritte)
        if (this.keys['ArrowUp'] || this.keys['w'] || this.keys['W']) {
          this.taxi.speed = Math.min(this.taxi.speed + 1, this.taxi.maxSpeed);
          this.lastSpeedChange = currentTime;
        }
        
        // Abbremsen bei gedrücktem Pfeil nach unten (5er-Schritte)
        if (this.keys['ArrowDown'] || this.keys['x'] || this.keys['X']) {
          this.taxi.speed = Math.max(this.taxi.speed - 1, 0); // Bremsen bis 0, nicht rückwärts
          this.lastSpeedChange = currentTime;
        }
      }
      
      // Lenkung in diskreten 11,25°-Schritten mit geschwindigkeitsabhängiger Verzögerung
      const steeringAngle = Math.PI / 16; // 11,25°
      const speedLevel = this.taxi.speed; // 0..24 (5 km/h je Stufe)
      // Nicht-lineare Beschleunigung der Eingabe: schneller bei hoher Geschwindigkeit
      const minInterval = 4;    // schnellste Annahme (ms) - erneut halbiert
      const maxInterval = 60;   // langsamste Annahme (ms) - erneut halbiert
      const factor = Math.pow(speedLevel / this.taxi.maxSpeed, 1.5); // 0..1, gewichtet
      const steeringInterval = Math.max(minInterval, maxInterval - (maxInterval - minInterval) * factor);

      // Richtung bestimmen (-1 links, +1 rechts)
      let steerDir = 0;
      if (this.keys['ArrowLeft'] || this.keys['a'] || this.keys['A']) steerDir = -1;
      if (this.keys['ArrowRight'] || this.keys['d'] || this.keys['D']) steerDir = steerDir === -1 ? 0 : 1; // beide gedrückt -> neutral

      if (steerDir !== 0) {
        const elapsed = currentTime - this.lastSteerChange;
        const steps = Math.floor(elapsed / steeringInterval);
        if (steps > 0) {
          this.taxi.angle += steerDir * steeringAngle * steps;
          // Restzeit beibehalten, damit Timing gleichmäßig bleibt
          this.lastSteerChange = currentTime - (elapsed % steeringInterval);
        }
      } else {
        // Keine Lenkung gedrückt: Timer resetten, um Burst bei erstem Druck zu vermeiden
        this.lastSteerChange = currentTime;
      }
      
      // Vorherige Position merken für Richtungs-/Grenzprüfungen
      this.prevTaxiX = this.taxi.x;
      this.prevTaxiY = this.taxi.y;
      // Aktualisiere Position (+20% Geschwindigkeit)
      this.taxi.x += Math.cos(this.taxi.angle) * this.taxi.speed * 0.12;
      this.taxi.y += Math.sin(this.taxi.angle) * this.taxi.speed * 0.12;
      
      // Aktualisiere aktuelle Tile-Position
      this.updateCurrentTile();
      
      // Verbrauche Treibstoff
      if (Math.abs(this.taxi.speed) > 0.1) {
        this.fuel = Math.max(0, this.fuel - 0.01);
      }
      
      // Prüfe auf leeren Tank - Crash wenn Tank leer
      if (this.fuel <= 0) {
        this.handleEmptyTank();
        return; // Stoppe weitere Verarbeitung nach Crash
      }
      
      // Prüfe Tanken-Bedingungen
      if (this.checkRefuelingConditions()) {
        if (!this.isRefueling) {
          this.startRefueling();
        }
      } else {
        if (this.isRefueling) {
          this.stopRefueling();
        }
      }
      
      // Motorgeräusch aktualisieren
      this.updateMotorSound();
    },
    
    handlePassengerActions() {
      // WICHTIG: Zuerst absetzen, dann einladen!
      // Automatisches Absetzen von geladenen Passagieren
      this.checkForPassengerDropoff();
      
      // Automatisches Einladen von wartenden Passagieren
      this.checkForWaitingPassengers();
      
      // S - Passagier aufnehmen (manuell)
      if (this.keys['s'] || this.keys['S']) {
        this.pickupPassenger();
      }
      
      // Q - Passagier absetzen
      if (this.keys['q'] || this.keys['Q']) {
        this.dropoffPassenger();
      }
      
      // Enter - Tanken
      if (this.keys['Enter']) {
        this.refuel();
      }
    },

    checkForWaitingPassengers() {
      // Nur einladen wenn das Taxi steht (Geschwindigkeit = 0)
      if (this.taxi.speed > 0) return;
      
      // Prüfe alle wartenden Passagiere
      for (let i = this.waitingPassengersList.length - 1; i >= 0; i--) {
        const passenger = this.waitingPassengersList[i];
        
        // Berechne die Position des Hauses auf dem aktuellen Tile
        const housePosition = this.getHousePositionOnTile(passenger.tileX, passenger.tileY, passenger.houseCorner);
        if (!housePosition) {
          continue;
        }
        
        // Prüfe ob das Taxi in der Nähe des Hauses ist
        const isNear = this.isTaxiNearHouse(housePosition, passenger.houseCorner);
        
        if (isNear) {
          // Lade Passagier ein
          this.pickupWaitingPassenger(passenger, i);
          break; // Nur einen Passagier pro Frame einladen
        }
      }
    },

    getHousePositionOnTile(tileX, tileY, corner) {
      // Prüfe ob das Taxi auf dem gleichen Tile ist wie das Haus
      if (this.currentTile.col !== tileX || this.currentTile.row !== tileY) {
        return null;
      }
      
      // Berechne die Position des Hauses basierend auf der Ecke
      // Diese Methode muss exakt mit drawHousesOnMainCanvas übereinstimmen
      const tileSize = 500; // Tile-Größe
      const HOUSE_W = 150; // Hausbreite
      const HOUSE_H = 150; // Haushöhe
      const pad = 30; // Abstand vom Rand
      
      let houseX, houseY;
      
      switch (corner) {
        case 'lo': // links-outer
          houseX = pad;
          houseY = pad;
          break;
        case 'ro': // rechts-outer
          houseX = tileSize - HOUSE_W - pad;
          houseY = pad;
          break;
        case 'lu': // links-upper
          houseX = pad;
          houseY = tileSize - HOUSE_H - pad;
          break;
        case 'ru': // rechts-upper
          houseX = tileSize - HOUSE_W - pad;
          houseY = tileSize - HOUSE_H - pad;
          break;
        default:
          return null;
      }
      
      return { x: houseX, y: houseY, corner };
    },

    isTaxiNearHouse(housePosition, corner) {
      const taxiX = this.taxi.x;
      const taxiY = this.taxi.y;
      const houseX = housePosition.x;
      const houseY = housePosition.y;
      const HOUSE_W = 150; // Hausbreite
      const HOUSE_H = 150; // Haushöhe
      const maxDistance = 100; // 100px Entfernung
      
      // Berechne die Entfernung zur Tile-Mitte
      const tileCenterX = 250; // Mitte des 500px Canvas
      const tileCenterY = 250; // Mitte des 500px Canvas
      const distanceToTileCenter = Math.sqrt(
        Math.pow(taxiX - tileCenterX, 2) + Math.pow(taxiY - tileCenterY, 2)
      );
      
      // Wenn das Taxi zu weit von der Tile-Mitte entfernt ist, keine Aktion
      const maxDistanceFromCenter = 200; // 200px von der Mitte entfernt
      if (distanceToTileCenter > maxDistanceFromCenter) {
        return false;
      }
      
      // Richtungsabhängige Erkennung basierend auf der Haus-Ecke
      let isNear = false;
      
      if (corner === 'lo' || corner === 'ro') {
        // Horizontale Straße - Taxi muss links oder rechts vom Haus sein
        const houseCenterX = houseX + HOUSE_W / 2;
        const houseCenterY = houseY + HOUSE_H / 2;
        
        // Prüfe horizontale Distanz (Taxi muss seitlich vom Haus sein)
        const horizontalDistance = Math.abs(taxiX - houseCenterX);
        const verticalDistance = Math.abs(taxiY - houseCenterY);
        
        // Taxi muss horizontal nah genug sein, aber vertikal kann es weiter weg sein
        isNear = horizontalDistance <= maxDistance && verticalDistance <= maxDistance * 1.5;
        
      } else if (corner === 'lu' || corner === 'ru') {
        // Vertikale Straße - Taxi muss oberhalb oder unterhalb vom Haus sein
        const houseCenterX = houseX + HOUSE_W / 2;
        const houseCenterY = houseY + HOUSE_H / 2;
        
        // Prüfe vertikale Distanz (Taxi muss oberhalb/unterhalb vom Haus sein)
        const horizontalDistance = Math.abs(taxiX - houseCenterX);
        const verticalDistance = Math.abs(taxiY - houseCenterY);
        
        // Taxi muss vertikal nah genug sein, aber horizontal kann es weiter weg sein
        isNear = verticalDistance <= maxDistance && horizontalDistance <= maxDistance * 1.5;
      }
      
      return isNear;
    },

    pickupWaitingPassenger(passenger, index) {
      
      // Entferne Passagier aus der Warteliste
      this.waitingPassengersList.splice(index, 1);
      
      // Gib das Haus wieder frei
      if (passenger.houseId) {
        this.occupiedHouses.delete(passenger.houseId);
      }
      
      // Generiere ein Ziel für den Passagier
      const destination = this.generatePassengerDestination();
      
      if (destination && destination.streetName && destination.streetName !== "Unbekannte Straße") {
        // Berechne Bonus und Zeit basierend auf kürzestem Weg
        const startX = passenger.tileX;
        const startY = passenger.tileY;
        const endX = destination.tileX;
        const endY = destination.tileY;
        
        const bonusData = this.calculateBonusAndTime(startX, startY, endX, endY);
        
        // Füge Passagier zur geladenen Liste hinzu
        const now = Date.now();
        this.loadedPassengersList.push({
          ...passenger,
          destination: destination,
          pickedUpAt: now,
          bonusData: bonusData,
          timeLeft: bonusData.maxTime,
          lastUpdateTime: 0, // Timer-Aktualisierung erst nach expliziter Aktivierung
          timerActive: false // Flag um Timer-Aktivierung zu kontrollieren
        });
      } else {
      }
      
      // Optional: Sound-Effekt oder Nachricht
      // this.playPickupSound();
    },

    checkForPassengerDropoff() {
      // Nur absetzen wenn das Taxi steht (Geschwindigkeit = 0)
      if (this.taxi.speed > 0) return;
      
      // Prüfe ob geladene Passagiere abgesetzt werden können
      if (this.loadedPassengersList.length === 0) return;
      
      // Prüfe alle geladene Passagiere
      for (let i = this.loadedPassengersList.length - 1; i >= 0; i--) {
        const passenger = this.loadedPassengersList[i];
        const destination = passenger.destination;
        
        // Prüfe ob das Taxi am Zielort ist
        if (this.currentTile.col === destination.tileX && this.currentTile.row === destination.tileY) {
          // Berechne die Position des Zielhauses auf dem aktuellen Tile
          const housePosition = this.getHousePositionOnTile(destination.tileX, destination.tileY, destination.houseCorner);
          if (housePosition) {
            // Prüfe ob das Taxi in der Nähe des Zielhauses ist
            const isNear = this.isTaxiNearHouse(housePosition, destination.houseCorner);
            
            if (isNear) {
              // Setze Passagier ab
              console.log('✅ Passagier abgesetzt:', passenger.name, 'an', destination.location);
              this.dropoffLoadedPassenger(passenger, i);
              break; // Nur einen Passagier pro Frame absetzen
            }
          }
        }
      }
    },

    dropoffLoadedPassenger(passenger, index) {
      // Entferne Passagier aus der geladenen Liste
      this.loadedPassengersList.splice(index, 1);
      
      // Berechne Bonus basierend auf verbleibender Zeit
      let bonus = 0;
      if (passenger.bonusData && passenger.timeLeft > 0) {
        // Bonus wird nur vergeben, wenn noch Zeit übrig ist
        bonus = passenger.bonusData.bonus;
      }
      
      // Belohne den Spieler
      this.passengersDelivered++;
      this.score += 50 + bonus;
      this.money += 25 + Math.floor(bonus / 2);
      
      console.log(`Passagier abgesetzt: ${passenger.name}, Bonus: ${bonus}, Zeit übrig: ${passenger.timeLeft}s`);
      
      // Optional: Sound-Effekt oder Nachricht
      // this.playDropoffSound();
    },
    
    pickupPassenger() {
      // Finde nächsten Passagier in der Nähe
      for (let i = 0; i < this.passengers.length; i++) {
        const passenger = this.passengers[i];
        if (!passenger.pickedUp && this.checkCollision(this.taxi, passenger)) {
          passenger.pickedUp = true;
          this.score += 10;
          break;
        }
      }
    },
    
    dropoffPassenger() {
      // Finde nächstes Ziel in der Nähe
      for (let i = 0; i < this.destinations.length; i++) {
        const destination = this.destinations[i];
        if (!destination.completed && this.checkCollision(this.taxi, destination)) {
          destination.completed = true;
          this.passengersDelivered++;
          this.score += 50;
          this.money += 25;
          // Entferne Passagier aus der Warteliste und gib Haus frei
          this.removePassengerFromWaitingList();
          break;
        }
      }
    },
    
    refuel() {
      // Prüfe ob Tankstellen verfügbar sind
      if (!this.gasStations || this.gasStations.length === 0) {
        console.log('Keine Tankstellen verfügbar');
        return;
      }
      
      // Finde nächste Tankstelle in der Nähe
      for (let i = 0; i < this.gasStations.length; i++) {
        const station = this.gasStations[i];
        if (this.checkCollision(this.taxi, station)) {
          this.fuel = Math.min(100, this.fuel + 50);
          this.score += 5;
          break;
        }
      }
    },
    
    checkCollisions() {
      // Prüfe Straßenkollisionen nur wenn das Spiel nicht pausiert ist
      if (!this.isPaused && !this.isTaxiOnRoad()) {
        this.handleCrash();
      }
      // Rotlichtverstöße prüfen
      if (!this.isPaused) {
        if (!this.skipRedLightOneFrame) {
          this.checkRedLightViolation();
        }
        // Einmal-Flag nach der Prüfung zurücksetzen
        if (this.skipRedLightOneFrame) this.skipRedLightOneFrame = false;
      }
      
      // Prüfe Hindernisse nur wenn das Spiel nicht pausiert ist
      if (!this.isPaused) {
        this.obstacles.forEach(obstacle => {
          if (this.checkCollision(this.taxi, obstacle, true)) {
            this.handleCrash();
          }
        });
        
        // Prüfe Autos-Kollisionen
        this.cars.forEach(car => {
          if (this.checkCollision(this.taxi, car, true)) {
            this.handleCrash('auto');
          }
        });
      }
    },
    // Prüft Überfahren der (virtuell verdoppelten) Haltelinie aus der straßenzugewandten Seite
    checkRedLightViolation() {
      if (!this.currentMap || !this.currentMap.mapData) return;
      const tileSize = this.tiles.size;
      const tileType = this.getCurrentTileType();
      const approaches = this.getTileApproaches(tileType);
      if (!approaches.top && !approaches.right && !approaches.bottom && !approaches.left) return;
      if (!this.getTrafficLightFor(this.currentTile.col + (this.currentMap.offsetX||0), this.currentTile.row + (this.currentMap.offsetY||0))) return;

      const phase = this.getTrafficLightPhase(this.currentTile.col + (this.currentMap.offsetX||0), this.currentTile.row + (this.currentMap.offsetY||0));
      const rects = this.getVirtualStopLineRects(tileSize, approaches);
      // Kleine Toleranz für Segmenttreffer (dick=5px, daher +-4px ausreichend)
      const tol = 4;

      // Einseitige Prüfung je Richtung und Phasen-Logik
      // Zu prüfende Ecken laut Vorgabe:
      // Horizontal: links unten (BL) und rechts oben (TR) müssen rot sein, um Verstoß zu zählen
      // Vertikal: rechts unten (BR) und links oben (TL) müssen rot sein
      // Diagonale Synchronisierung: TL/BR sind vertikal gekoppelt, TR/BL horizontal
      const isHorRed = (phase === 2 || phase === 3);
      const isVerRed = (phase === 0 || phase === 1);

      let violated = false;

      // Von welcher Seite kommt das Taxi? Delta der Mittelpunkt-Position
      const prevCX = this.prevTaxiX + this.taxi.width / 2;
      const prevCY = this.prevTaxiY + this.taxi.height / 2;
      const currCX = this.taxi.x + this.taxi.width / 2;
      const currCY = this.taxi.y + this.taxi.height / 2;
      const vx = currCX - prevCX;
      const vy = currCY - prevCY;


      // Hinweis: Keine globale Bewegungsschwelle mehr.
      // Die Richtungsbedingungen (vy/vx > 0 bzw. < 0) in den jeweiligen Fällen
      // stellen bereits sicher, dass nur bei tatsächlicher Bewegung geprüft wird.


      // Top-Band: von oben nach unten (prev oben, curr unten), Eintritt über obere Bandkante y0
      if (rects.top && approaches.top) {
        const x0 = rects.top.x, x1 = rects.top.x + rects.top.width;
        const y0 = rects.top.y, y1 = rects.top.y + rects.top.height;
        // Für horizontale Haltelinien: Y-Position muss in der Nähe der Haltelinie sein
        const isNearStopLine = currCY >= (y0 - 50) && currCY <= (y1 + 50);
        // Zusätzlich: innerhalb des horizontalen Liniensegments (X-Span) sein
        const withinXSpan = (
          (prevCX >= x0 - tol && prevCX <= x1 + tol) ||
          (currCX >= x0 - tol && currCX <= x1 + tol) ||
          // oder Strecke zwischen prevCX und currCX schneidet [x0, x1]
          (Math.min(prevCX, currCX) <= x1 + tol && Math.max(prevCX, currCX) >= x0 - tol)
        );
        
        if (vy > 0 && prevCY < y0 && currCY >= y0 && isVerRed && isNearStopLine && withinXSpan) {
          violated = true;
        }
      }
      // Bottom-Band: von unten nach oben (prev unten, curr oben), Eintritt über untere Bandkante y1
      if (rects.bottom && approaches.bottom) {
        const x0 = rects.bottom.x, x1 = rects.bottom.x + rects.bottom.width;
        const y0 = rects.bottom.y, y1 = rects.bottom.y + rects.bottom.height;
        // Für horizontale Haltelinien: Y-Position muss in der Nähe der Haltelinie sein
        const isNearStopLine = currCY >= (y0 - 50) && currCY <= (y1 + 50);
        // Zusätzlich: innerhalb des horizontalen Liniensegments (X-Span) sein
        const withinXSpan = (
          (prevCX >= x0 - tol && prevCX <= x1 + tol) ||
          (currCX >= x0 - tol && currCX <= x1 + tol) ||
          (Math.min(prevCX, currCX) <= x1 + tol && Math.max(prevCX, currCX) >= x0 - tol)
        );
        
        if (vy < 0 && prevCY > y1 && currCY <= y1 && isVerRed && isNearStopLine && withinXSpan) {
          violated = true;
        }
      }
      // Left-Band: von links nach rechts (prev links, curr rechts), Eintritt über linke Bandkante x0
      if (rects.left && approaches.left) {
        const x0 = rects.left.x, x1 = rects.left.x + rects.left.width;
        const y0 = rects.left.y, y1 = rects.left.y + rects.left.height;
        // Für vertikale Haltelinien: X-Position muss in der Nähe der Haltelinie sein
        const isNearStopLine = currCX >= (x0 - 50) && currCX <= (x1 + 50);
        // Zusätzlich: innerhalb des vertikalen Liniensegments (Y-Span) sein
        const withinYSpan = (
          (prevCY >= y0 - tol && prevCY <= y1 + tol) ||
          (currCY >= y0 - tol && currCY <= y1 + tol) ||
          (Math.min(prevCY, currCY) <= y1 + tol && Math.max(prevCY, currCY) >= y0 - tol)
        );
        
        if (vx > 0 && prevCX < x0 && currCX >= x0 && isHorRed && isNearStopLine && withinYSpan) {
          violated = true;
        }
      }
      // Right-Band: von rechts nach links (prev rechts, curr links), Eintritt über rechte Bandkante x1
      if (rects.right && approaches.right) {
        const x0 = rects.right.x, x1 = rects.right.x + rects.right.width;
        const y0 = rects.right.y, y1 = rects.right.y + rects.right.height;
        // Für vertikale Haltelinien: X-Position muss in der Nähe der Haltelinie sein
        const isNearStopLine = currCX >= (x0 - 50) && currCX <= (x1 + 50);
        // Zusätzlich: innerhalb des vertikalen Liniensegments (Y-Span) sein
        const withinYSpan = (
          (prevCY >= y0 - tol && prevCY <= y1 + tol) ||
          (currCY >= y0 - tol && currCY <= y1 + tol) ||
          (Math.min(prevCY, currCY) <= y1 + tol && Math.max(prevCY, currCY) >= y0 - tol)
        );
        
        if (vx < 0 && prevCX > x1 && currCX <= x1 && isHorRed && isNearStopLine && withinYSpan) {
          violated = true;
        }
      }

      
      if (violated) {
        // Entprellen: pro Tile nur einmal pro tatsächlichem Übertritt zählen
        const key = `${this.currentTile.col},${this.currentTile.row}`;
        const now = Date.now();
        const last = this.redLightLastCount[key] || 0;
        console.log('Violation detected! Key:', key, 'Last:', last, 'Now:', now, 'Diff:', now - last);
        
        if (now - last > 500) { // 0.5s Sperrzeit
          console.log('✅ VIOLATION REGISTERED! Count:', this.redLightViolations + 1);
          this.redLightLastCount[key] = now;
          this.redLightViolations += 1;
          this.playRedLightSound();
          // Drei Rotlichtverstöße => Crash-Penalty
          this.redLightSincePenalty = (this.redLightSincePenalty || 0) + 1;
          if (this.redLightSincePenalty >= 3) {
            this.redLightSincePenalty = 0;
            this.countCrash('redlight');
            const msg = 'Du hast wegen Rotlicht-Übertretungen ein Auto verloren.';
            const title = 'Hinweis';
            this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
              if (this.vehicleCount === 0) {
                this.handleOutOfVehicles();
              }
            });
          }
        }
      }
    },

    checkRadarMeasurement() {
      if (!this.activeRadar || this.radarTicketShown || !this.radarAxis) return;
      const size = this.tiles.size;
      const thresholdSpeedLevel = 10; // >50 km/h
      const speedLevel = this.taxi.speed;

      const prevCX = this.prevTaxiX + this.taxi.width / 2;
      const prevCY = this.prevTaxiY + this.taxi.height / 2;
      const currCX = this.taxi.x + this.taxi.width / 2;
      const currCY = this.taxi.y + this.taxi.height / 2;

      if (this.radarAxis === 'H') {
        const y = this.radarLinePos;
        // Richtung beachten: von oben nach unten über y (wenn y nahe oben), sonst umgekehrt
        if (y <= size / 2) {
          if (speedLevel > thresholdSpeedLevel && prevCY < y && currCY >= y) this.issueSpeedTicket();
        } else {
          if (speedLevel > thresholdSpeedLevel && prevCY > y && currCY <= y) this.issueSpeedTicket();
        }
      } else if (this.radarAxis === 'V') {
        const x = this.radarLinePos;
        if (x <= size / 2) {
          if (speedLevel > thresholdSpeedLevel && prevCX < x && currCX >= x) this.issueSpeedTicket();
        } else {
          if (speedLevel > thresholdSpeedLevel && prevCX > x && currCX <= x) this.issueSpeedTicket();
        }
      }
    },

    issueSpeedTicket() {
      this.radarTicketShown = true;
      this.speedViolations += 1;
      this.playCameraSound();
    },

    playCameraSound() {
      try {
        this.ensureAudioUnlockedInEvent();
        if (!this.audioContext) return;
        const now = this.audioContext.currentTime;
        const osc = this.audioContext.createOscillator();
        const gain = this.audioContext.createGain();
        // kurzer Klick (Kamera-Shutter)
        osc.type = 'square';
        osc.frequency.setValueAtTime(2400, now);
        gain.gain.setValueAtTime(0, now);
        gain.gain.linearRampToValueAtTime(0.4, now + 0.005);
        gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.06);
        osc.connect(gain).connect(this.audioContext.destination);
        osc.start(now);
        osc.stop(now + 0.07);
      } catch (e) {
        // ignore
      }
    },

    playRedLightSound() {
      // Ein kurzer Piepton über die bestehende AudioContext-Pipeline
      try {
        this.ensureAudioUnlockedInEvent();
        if (!this.audioContext) return;
        const now = this.audioContext.currentTime;
        // Entprellen global: max 1x pro 550ms (Ton ist länger)
        const wall = (this._rlLastWall || 0);
        if (now < wall) return;
        this._rlLastWall = now + 0.55;

        const osc = this.audioContext.createOscillator();
        const gain = this.audioContext.createGain();
        osc.type = 'square';
        // etwas höher
        osc.frequency.setValueAtTime(1320, now);
        gain.gain.setValueAtTime(0, now);
        // etwas lauter und länger
        gain.gain.linearRampToValueAtTime(0.35, now + 0.01);
        gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.5);
        osc.connect(gain).connect(this.audioContext.destination);
        osc.start(now);
        osc.stop(now + 0.52);
      } catch (e) {
        // ignore
      }
    },
    
    countCrash(reason = '') {
      this.crashes++;
      this.decrementVehicle(reason);
      this.taxi.speed = 0;
      this.isPaused = true; // Zuerst pausieren
      this.showPauseOverlay = true; // Overlay sichtbar machen
      
      // Alle KI-Autos sofort entfernen
      try { this.cars = []; } catch (_) {}
      
      // Game Loop stoppen
      if (this.gameLoop) {
        cancelAnimationFrame(this.gameLoop);
        this.gameLoop = null;
      }
      
      // Motor sofort stoppen
      try { if (this.motorSound && this.motorSound.isPlaying) this.motorSound.stop(); } catch (_) {}

      // Eingaben zurücksetzen, sonst bleibt "beschleunigen" aus
      this.keys = {};
      this.lastSpeedChange = 0;

      this.fuel = 100;
    },

    handleCrash() {
      // Verhindere mehrfache Crashes in kurzer Zeit
      if (this.isPaused) {
        console.log('Crash bereits erkannt, ignoriere weitere');
        return;
      }
      
      console.log('🚨 CRASH ERKANNT!');
      this.countCrash('wall crashed');

      // Motorgeräusch sofort stoppen
      if (this.motorSound && this.motorSound.isPlaying) {
        this.motorSound.stop();
      }
      
      // Taxi mittig im aktuellen Tile platzieren
      this.taxi.speed = 0;
      this.taxi.angle = 0;
      this.centerTaxiInCurrentTile();
      
      // Dialog über globale MessageDialog öffnen
      this.$nextTick(() => {
        // Temporär direkte Übersetzung verwenden, bis i18n-Problem gelöst ist
        const crashMessage = `Du hattest einen Unfall! Crashes: ${this.crashes}`;
        const crashTitle = 'Unfall!';
        this.$root?.$refs?.messageDialog?.open?.(crashMessage, crashTitle, {}, this.handleCrashDialogClose);
        
        // Test: Direkter Aufruf nach 3 Sekunden (falls Dialog-Callback nicht funktioniert)
        this.crashDialogTimeout = setTimeout(() => {
          this.handleCrashDialogClose();
        }, 3000);
        console.log('Crash-Dialog wird angezeigt:', {
          crashes: this.crashes,
          isPaused: this.isPaused,
          taxiSpeed: this.taxi.speed
        });
        
        // Spiel bleibt pausiert bis Dialog geschlossen wird
      });
    },
    
    handleCrashDialogClose() {
      console.log('Crash-Dialog geschlossen, Spiel wird fortgesetzt');
      this.isPaused = false;
      this.showPauseOverlay = false;
      
      console.log('Nach Dialog-Close - isPaused:', this.isPaused, 'showPauseOverlay:', this.showPauseOverlay);
      
      // Game Loop sicher neu starten (unabhängig vom vorherigen Zustand)
      this.gameRunning = true;
      try { cancelAnimationFrame(this.gameLoop); } catch (_) {}
      this.gameLoop = requestAnimationFrame(this.update);
      
      // Taxi bleibt auf dem aktuellen Tile, mittig platzieren
      this.taxi.speed = 0;
      this.taxi.angle = 0;
      this.centerTaxiInCurrentTile();
      
      // Eingabestatus zurücksetzen, damit Beschleunigen wieder funktioniert
      this.keys = {};
      this.lastSpeedChange = 0;
      // Key-Listener frisch registrieren und Fokus auf Canvas erzwingen
      try { document.removeEventListener('keydown', this.handleKeyDown); } catch (_) {}
      document.addEventListener('keydown', this.handleKeyDown);
      try { this.canvas && this.canvas.focus && this.canvas.focus(); } catch (_) {}
      // Motor neu initialisieren, falls erforderlich
      try {
        if (this.motorSound && this.motorSound.isPlaying) this.motorSound.stop();
        if (this.motorSound && !this.motorSound.isInitialized && this.audioContext) {
          this.motorSound.init();
        }
      } catch (_) {}
      
      // Fokus zurück auf Canvas setzen
      this.$nextTick(() => {
        if (this.canvas) {
          this.canvas.focus();
        }
        console.log('Nach nextTick - isPaused:', this.isPaused);
        if (this.vehicleCount === 0) {
          this.handleOutOfVehicles();
        }
      });
    },

    decrementVehicle(reason = '') {
      this.vehicleCount = Math.max(0, this.vehicleCount - 1);
    },

    getPlayTime() {
      if (!this.gameStartTime) return 0;
      return Math.floor((Date.now() - this.gameStartTime) / 1000);
    },

    async saveHighscore() {
      try {
        const playTime = this.getPlayTime();
        const highscoreData = {
          passengersDelivered: this.passengersDelivered,
          playtime: playTime,
          points: this.score,
          mapId: this.currentMap ? this.currentMap.id : null
        };

        console.log('Highscore-Daten:', highscoreData);
        console.log('Current Map:', this.currentMap);
        console.log('Passengers Delivered:', this.passengersDelivered);
        console.log('Playtime:', playTime);
        console.log('Points:', this.score);

        const response = await apiClient.post('/api/taxi/highscore', highscoreData);
        
        if (response.data.success) {
          console.log('Highscore erfolgreich gespeichert:', response.data.data);
          return response.data.data;
        } else {
          console.error('Fehler beim Speichern des Highscores:', response.data.message);
          return null;
        }
      } catch (error) {
        console.error('Fehler beim Speichern des Highscores:', error);
        if (error.response) {
          console.error('Backend-Fehler:', error.response.data);
          console.error('Status:', error.response.status);
        }
        return null;
      }
    },

    async handleOutOfVehicles() {
      // Spiel stoppen
      this.isPaused = true;
      this.gameLoop = null;
      
      // Highscore speichern
      const highscore = await this.saveHighscore();
      
      const playTime = this.getPlayTime();
      const playTimeMinutes = Math.floor(playTime / 60);
      const playTimeSeconds = playTime % 60;
      
      const title = 'Spiel beendet!';
      const msg = `Keine Fahrzeuge mehr. Spiel beendet!\n\n` +
                  `Deine Leistung:\n` +
                  `• Passagiere: ${this.passengersDelivered}\n` +
                  `• Punkte: ${this.score}\n` +
                  `• Spielzeit: ${playTimeMinutes}:${playTimeSeconds.toString().padStart(2, '0')}\n` +
                  `• Map: ${this.currentMap ? this.currentMap.name : 'Unbekannt'}\n\n` +
                  `Highscore wurde gespeichert!`;
      
      this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
        this.restartLevel();
        this.vehicleCount = 5;
        this.crashes = 0;
        this.redLightViolations = 0;
        this.redLightSincePenalty = 0;
        this.gameStartTime = null;
      });
    },
    
    resetTaxiPosition() {
      // Taxi in der Mitte des Screens platzieren
      this.taxi.x = 250 - this.taxi.width/2;
      this.taxi.y = 250 - this.taxi.height/2;
      this.taxi.speed = 0;
      this.taxi.angle = 0;
      
      // Aktuelle Tile-Position zurücksetzen
      this.currentTile.row = 0;
      this.currentTile.col = 0;
    },
    
    centerTaxiInCurrentTile() {
      // Taxi mittig im aktuellen Tile platzieren (ohne Tile-Position zu ändern)
      this.taxi.x = 250 - this.taxi.width/2;  // Mitte des Canvas (500px / 2)
      this.taxi.y = 250 - this.taxi.height/2; // Mitte des Canvas (500px / 2)
      this.taxi.speed = 0;
      this.taxi.angle = 0;
      // Prev-Position synchronisieren und erste Prüfung aussetzen
      this.prevTaxiX = this.taxi.x;
      this.prevTaxiY = this.taxi.y;
      this.skipRedLightOneFrame = true;
    },
    
    isTaxiOnRoad() {
      // Prüfe ob das Taxi innerhalb der Canvas-Grenzen ist
      if (this.taxi.speed < 1) {
        return true;
      }
      if (this.taxi.x < 0 || this.taxi.x >= this.canvas.width || 
          this.taxi.y < 0 || this.taxi.y >= this.canvas.height) {
        return false;
      }
      // Hole das aktuelle Tile und dessen Polygone
      const tileType = this.getCurrentTileType();
      const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
      const originalTileSize = streetCoordinates.getOriginalTileSize(); // 640px
      const currentTileSize = 500; // Aktuelle Render-Größe
      
      // Hole die relativen Koordinaten aus der JSON (0-1) und konvertiere zu absoluten Pixeln
      const tileData = streetCoordinates.data.tiles[streetTileType];
      const regions = tileData ? tileData.regions : [];
      
      if (regions.length === 0) {
        // Keine Polygone definiert = befahrbar
        return true;
      }
      
      // Erstelle Taxi-Rechteck in absoluten Pixel-Koordinaten (500x500px)
      const taxiRect = {
        x: this.taxi.x,
        y: this.taxi.y,
        width: this.taxi.width,
        height: this.taxi.height
      };

      // Alle Tiles verwenden die gleiche Logik: Prüfe ob Taxi eine der Hindernis-Polylinien schneidet
      
      // Erstelle rotiertes Rechteck in absoluten Pixel-Koordinaten (500x500px)
      const rotatedRect = {
        cx: taxiRect.x + taxiRect.width / 2,  // Mittelpunkt X
        cy: taxiRect.y + taxiRect.height / 2, // Mittelpunkt Y
        theta: this.taxi.imageAngle + this.taxi.angle, // Rotation
        hx: taxiRect.width / 2,           // halbe Breite
        hy: taxiRect.height / 2           // halbe Höhe
      };
      
      // Prüfe jede Region (Polylinie) einzeln
      for (let i = 0; i < regions.length; i++) {
        const region = regions[i];
        // Jede Region ist eine Polylinie mit mehreren Punkten
        if (region.length >= 2) {
          // Konvertiere relative Koordinaten (0-1) zu absoluten Pixeln (500x500px)
          const regionPoints = region.map(point => ({
            x: point.x * currentTileSize, // Konvertiere von 0-1 zu 0-500px
            y: point.y * currentTileSize  // Konvertiere von 0-1 zu 0-500px
          }));
          
          // Verwende die robuste Kollisionserkennung für diese Region
          const result = this.polylineIntersectsRotatedRect(regionPoints, rotatedRect);
          
          // Einheitliche Logik für alle Tiles: Taxi darf KEINE der Hindernis-Polylinien schneiden
          if (result.hit) {
            return false; // Kollision mit Hindernis = Crash
          }
        }
      }
      
      // Alle Regionen geprüft - einheitliche Logik für alle Tiles
      // Keine Hindernis-Polylinie geschnitten = befahrbar
      return true; // Keine Kollision = befahrbar
    },
    
    // Prüft ob eine Linie ein rotiertes Rechteck schneidet
    isLineCrossingRect(rect, line) {
      // Verwende absolute Pixel-Koordinaten (500x500px)
      const currentTileSize = 500; // Aktuelle Render-Größe
      
      // Verwende Rechteck direkt in absoluten Koordinaten
      const absoluteRect = {
        x: rect.x,
        y: rect.y,
        width: rect.width,
        height: rect.height
      };
      
      // Konvertiere Linien-Koordinaten von 640px zu absoluten Pixeln (500x500px)
      const absoluteLine = {
        x1: (line.x1 / 640) * currentTileSize,
        y1: (line.y1 / 640) * currentTileSize,
        x2: (line.x2 / 640) * currentTileSize,
        y2: (line.y2 / 640) * currentTileSize
      };
      
      // Erstelle Polyline aus der absoluten Linie
      const polyline = [
        { x: absoluteLine.x1, y: absoluteLine.y1 },
        { x: absoluteLine.x2, y: absoluteLine.y2 }
      ];
      
      // Erstelle rotiertes Rechteck in absoluten Koordinaten (500x500px)
      const rotatedRect = {
        cx: absoluteRect.x + absoluteRect.width / 2,  // Mittelpunkt X
        cy: absoluteRect.y + absoluteRect.height / 2, // Mittelpunkt Y
        theta: this.taxi.imageAngle + this.taxi.angle, // Rotation
        hx: absoluteRect.width / 2,           // halbe Breite
        hy: absoluteRect.height / 2           // halbe Höhe
      };
      
      // Verwende die robuste Kollisionserkennung
      const result = this.polylineIntersectsRotatedRect(polyline, rotatedRect);
      
      return result.hit;
    },
    
    // Testet, ob eine Polyline (Liste von Punkten) ein rotiertes Rechteck schneidet
    polylineIntersectsRotatedRect(polyline, rect) {
      const { cx, cy, theta, hx, hy } = rect;
      
      // Transformiere die Polyline in das lokale Koordinatensystem des Rechtecks
      const localPolyline = polyline.map(p => this.transformToLocal(p.x, p.y, cx, cy, theta));

      // AABB-Grenzen im lokalen Koordinatensystem
      const AABB_minX = -hx;
      const AABB_maxX = hx;
      const AABB_minY = -hy;
      const AABB_maxY = hy;

      // 1. Prüfe, ob transformierte Polyline-Punkte in der AABB liegen
      for (let i = 0; i < localPolyline.length; i++) {
        const p = localPolyline[i];
        const inX = p.x >= AABB_minX && p.x <= AABB_maxX;
        const inY = p.y >= AABB_minY && p.y <= AABB_maxY;
        if (inX && inY) {
          return { hit: true, type: 'point_in_rect' };
        }
      }

      // 2. Prüfe auf Segment-Schnitt mit der AABB im lokalen System
      for (let i = 0; i < localPolyline.length - 1; i++) {
        const A = localPolyline[i];
        const B = localPolyline[i + 1];

        if (this.lineSegmentIntersectsRect(A.x, A.y, B.x, B.y, AABB_minX, AABB_minY, AABB_maxX, AABB_maxY)) {
          return { hit: true, type: 'segment_intersects_rect' };
        }
      }

      return { hit: false };
    },

    /**
     * Transformiert einen Punkt in das lokale, achsparallele Koordinatensystem des Rechtecks.
     * @param {number} px - X-Koordinate des Punktes
     * @param {number} py - Y-Koordinate des Punktes  
     * @param {number} cx - X-Koordinate des Rechteckmittelpunkts
     * @param {number} cy - Y-Koordinate des Rechteckmittelpunkts
     * @param {number} theta - Rotationswinkel des Rechtecks (in Radiant)
     * @returns {Object} Transformierter Punkt {x, y} im lokalen Koordinatensystem
     */
    transformToLocal(px, py, cx, cy, theta) {
      // 1. Translation: Verschieben des Rechteckmittelpunkts in den Ursprung (0,0)
      const tx = px - cx;
      const ty = py - cy;
      
      // 2. Rotation: Gegen-Rotation um -theta
      const cos = Math.cos(-theta);
      const sin = Math.sin(-theta);
      
      const lx = tx * cos - ty * sin;
      const ly = tx * sin + ty * cos;
      
      return { x: lx, y: ly };
    },
    
    // Prüft ob ein Punkt auf einem Liniensegment liegt
    isPointOnLineSegment(px, py, x1, y1, x2, y2, tolerance = 0.01) {
      // Berechne den Abstand vom Punkt zur Linie
      const A = px - x1;
      const B = py - y1;
      const C = x2 - x1;
      const D = y2 - y1;
      
      const dot = A * C + B * D;
      const lenSq = C * C + D * D;
      
      if (lenSq === 0) {
        // Linie ist ein Punkt
        return Math.abs(px - x1) < tolerance && Math.abs(py - y1) < tolerance;
      }
      
      const param = dot / lenSq;
      
      // Prüfe ob Parameter im Bereich [0,1] liegt
      if (param < 0 || param > 1) {
        return false;
      }
      
      // Berechne den nächsten Punkt auf der Linie
      const xx = x1 + param * C;
      const yy = y1 + param * D;
      
      // Prüfe ob der Abstand klein genug ist
      const distance = Math.sqrt((px - xx) * (px - xx) + (py - yy) * (py - yy));
      return distance <= tolerance;
    },
    
    // Prüft ob ein Liniensegment ein Rechteck schneidet
    lineSegmentIntersectsRect(x1, y1, x2, y2, rectMinX, rectMinY, rectMaxX, rectMaxY) {
      // Verwende den Liang-Barsky Algorithmus für Liniensegment-Rechteck Schnitt
      let t0 = 0, t1 = 1;
      const dx = x2 - x1;
      const dy = y2 - y1;
      
      // Prüfe jede Seite des Rechtecks
      const sides = [
        { p: -dx, q: x1 - rectMinX },    // links
        { p: dx, q: rectMaxX - x1 },     // rechts
        { p: -dy, q: y1 - rectMinY },    // oben
        { p: dy, q: rectMaxY - y1 }      // unten
      ];
      
      for (const side of sides) {
        if (Math.abs(side.p) < 1e-9) {
          // Parallel zur Seite
          if (side.q < 0) {
            return false; // außerhalb
          }
        } else {
          const t = side.q / side.p;
          if (side.p < 0) {
            // Eintritt
            if (t > t0) t0 = t;
          } else {
            // Austritt
            if (t < t1) t1 = t;
          }
          
          if (t0 > t1) {
            return false; // keine Überschneidung
          }
        }
      }
      
      return t0 <= 1 && t1 >= 0;
    },
    
    // Prüft ob ein Punkt auf einer Linie liegt
    isPointOnLine(px, py, line) {
      const { x1, y1, x2, y2 } = line;
      
      // Berechne den Abstand vom Punkt zur Linie
      const A = px - x1;
      const B = py - y1;
      const C = x2 - x1;
      const D = y2 - y1;
      
      const dot = A * C + B * D;
      const lenSq = C * C + D * D;
      
      if (lenSq === 0) {
        // Linie ist ein Punkt
        return Math.abs(px - x1) < 0.001 && Math.abs(py - y1) < 0.001;
      }
      
      const param = dot / lenSq;
      
      let xx, yy;
      if (param < 0) {
        xx = x1;
        yy = y1;
      } else if (param > 1) {
        xx = x2;
        yy = y2;
      } else {
        xx = x1 + param * C;
        yy = y1 + param * D;
      }
      
      const dx = px - xx;
      const dy = py - yy;
      const distance = Math.sqrt(dx * dx + dy * dy);
      
      return distance < 0.001; // Toleranz für Gleitkommazahlen
    },
    
    // Prüft ob ein Punkt in einem Polygon liegt (Point-in-Polygon)
    isPointInPolygon(x, y, polygon) {
      let inside = false;
      for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
        const xi = polygon[i].x;
        const yi = polygon[i].y;
        const xj = polygon[j].x;
        const yj = polygon[j].y;
        
        // Ray casting algorithm: Prüfe ob der Strahl von (x,y) nach rechts die Kante schneidet
        if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
          inside = !inside;
        }
      }
      return inside;
    },
    
    // Prüft ob ein Punkt in einem Rechteck liegt
    isPointInRect(x, y, rect) {
      return x >= rect.x && x <= rect.x + rect.width && 
             y >= rect.y && y <= rect.y + rect.height;
    },
    
    // Prüft ob zwei Linien sich schneiden
    lineIntersection(line1, line2) {
      const x1 = line1.x1, y1 = line1.y1, x2 = line1.x2, y2 = line1.y2;
      const x3 = line2.x1, y3 = line2.y1, x4 = line2.x2, y4 = line2.y2;
      
      const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
      if (Math.abs(denom) < 1e-10) return false; // Parallele Linien
      
      const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
      const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom;
      
      return t >= 0 && t <= 1 && u >= 0 && u <= 1;
    },
    
    getCurrentTileType() {
      // Hole das aktuelle Tile aus der geladenen Map
      if (this.currentMap && this.currentMap.mapData) {
        const mapData = this.currentMap.mapData;
        const tile = mapData[this.currentTile.row] && mapData[this.currentTile.row][this.currentTile.col];
        
        // Prüfe ob tile ein String ist (direkter Tile-Name) oder ein Objekt mit type-Eigenschaft
        if (tile) {
          if (typeof tile === 'string') {
            return tile;
          } else if (tile.type) {
            return tile.type;
          }
        }
      }
      
      // Fallback: Standard-Tile
      return 'cornertopleft';
    },
    
    checkCollision(a, b, recordDebug = false) {
      // Präzise Kollision über OBB (oriented bounding boxes) mittels SAT
      // Fallback auf AABB, wenn Winkel fehlen
      const angleA = ((typeof a.angle === 'number') ? a.angle : 0) + ((typeof a.imageAngle === 'number') ? a.imageAngle : 0);
      const angleB = ((typeof b.angle === 'number') ? b.angle : 0) + ((typeof b.imageAngle === 'number') ? b.imageAngle : 0);

      // Wenn beide quasi unrotiert sind, nutze performantes AABB
      const small = 1e-6;
      if (Math.abs(angleA) < small && Math.abs(angleB) < small) {
        const pad = (a.isCar || b.isCar) ? 4 : 0;
        const aLeft   = a.x + pad;
        const aRight  = a.x + a.width - pad;
        const aTop    = a.y + pad;
        const aBottom = a.y + a.height - pad;
        const bLeft   = b.x + pad;
        const bRight  = b.x + b.width - pad;
        const bTop    = b.y + pad;
        const bBottom = b.y + b.height - pad;
        const hit = aLeft < bRight && aRight > bLeft && aTop < bBottom && aBottom > bTop;
        if (recordDebug) {
          this._collisionDebug = {
            aCorners: [
              {x:aLeft,y:aTop},{x:aRight,y:aTop},{x:aRight,y:aBottom},{x:aLeft,y:aBottom}
            ],
            bCorners: [
              {x:bLeft,y:bTop},{x:bRight,y:bTop},{x:bRight,y:bBottom},{x:bLeft,y:bBottom}
            ],
            ttl: 60
          };
        }
        return hit;
      }

      const pad = (a.isCar || b.isCar) ? 3 : 0; // etwas größerer Puffer bei OBB

      const obbA = this._buildObb(a, pad);
      const obbB = this._buildObb(b, pad);

      // Achsen: Normalen der Kanten beider Rechtecke (4 Achsen)
      const axes = [
        this._edgeAxis(obbA.corners[0], obbA.corners[1]),
        this._edgeAxis(obbA.corners[1], obbA.corners[2]),
        this._edgeAxis(obbB.corners[0], obbB.corners[1]),
        this._edgeAxis(obbB.corners[1], obbB.corners[2])
      ];

      for (let i = 0; i < axes.length; i++) {
        const axis = axes[i];
        const projA = this._projectOntoAxis(obbA.corners, axis);
        const projB = this._projectOntoAxis(obbB.corners, axis);
        if (projA.max < projB.min || projB.max < projA.min) {
          return false; // trennende Achse gefunden
        }
      }
      if (recordDebug) {
        this._collisionDebug = { aCorners: obbA.corners, bCorners: obbB.corners, ttl: 60 };
      }
      return true; // alle Intervalle überlappen
    },

    _buildObb(rect, pad = 0) {
      const cx = rect.x + rect.width / 2;
      const cy = rect.y + rect.height / 2;
      // Spezifische Kollisionsmaße:
      // - Taxi: schmaler als Zeichnungs-Bounding-Box (Bild enthält weiße Ränder)
      // - Auto: verwende reale width/height
      let w = rect.width;
      let h = rect.height;
      if (rect === this.taxi) {
        w = Math.max(0, rect.width * 0.70);  // 70% der visuellen Breite
        h = Math.max(0, rect.height * 0.90); // 90% der visuellen Höhe
      } else if (rect.isCar) {
        // Autos fahren nur links/rechts → Fahrzeuglänge = horizontal (w), Fahrzeugbreite = vertikal (h)
        // Daher: Länge kaum kürzen, Breite deutlich schmaler
        w = Math.max(0, rect.width * 0.90); // Länge ~90%
        h = Math.max(0, rect.height * 0.57); // Breite ~65%
      }
      const hw = Math.max(0, w / 2 - pad);
      const hh = Math.max(0, h / 2 - pad);
      const ang = ((rect.angle || 0) + (rect.imageAngle || 0));
      const c = Math.cos(ang);
      const s = Math.sin(ang);

      // lokale Ecken
      const local = [
        { x: -hw, y: -hh },
        { x:  hw, y: -hh },
        { x:  hw, y:  hh },
        { x: -hw, y:  hh }
      ];
      // rotiere + verschiebe
      const corners = local.map(p => ({ x: cx + p.x * c - p.y * s, y: cy + p.x * s + p.y * c }));
      return { corners };
    },

    _edgeAxis(p1, p2) {
      // Kantenrichtung
      const dx = p2.x - p1.x;
      const dy = p2.y - p1.y;
      // Normale (senkrecht)
      const nx = -dy;
      const ny = dx;
      // normieren
      const len = Math.hypot(nx, ny) || 1;
      return { x: nx / len, y: ny / len };
    },

    _projectOntoAxis(corners, axis) {
      let min = Infinity, max = -Infinity;
      for (let i = 0; i < corners.length; i++) {
        const p = corners[i];
        const val = p.x * axis.x + p.y * axis.y;
        if (val < min) min = val;
        if (val > max) max = val;
      }
      return { min, max };
    },
    
    render() {
      // Lösche Canvas
      this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
      
      // Zeichne Straßen
      this.drawRoads();

      // Radar-Icon oben rechts (50x50), wenn aktiv
      if (this.activeRadar) {
        const img = this.tiles.images['radar'];
        if (img && img.complete) {
          const size = 50;
          this.ctx.drawImage(img, this.canvas.width - size - 4, 4, size, size);
        }
      }
      
      // Zeichne Hindernisse
      this.obstacles.forEach(obstacle => {
        this.ctx.fillStyle = '#666';
        this.ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
      });
      
      // Passagiere/Ziele werden aktuell nicht gezeichnet
      
      // Zeichne Autos
      this.drawCars();
      
      // Debug: Kollision-Overlays zeichnen (falls vorhanden)
      if (this._collisionDebug && this._collisionDebug.ttl > 0) {
        const dbg = this._collisionDebug;
        this._drawDebugPoly(dbg.aCorners, 'rgba(255,0,0,0.35)', '#ff0000');
        this._drawDebugPoly(dbg.bCorners, 'rgba(0,128,255,0.35)', '#0080ff');
        dbg.ttl -= 1;
      }

      // Zeichne Taxi
      this.ctx.save();
      this.ctx.translate(this.taxi.x + this.taxi.width/2, this.taxi.y + this.taxi.height/2);
      this.ctx.rotate(this.taxi.imageAngle + this.taxi.angle);
      
      if (this.taxi.image) {
        // Zeichne Taxi-Bild
        // console.log('Zeichne Taxi-Bild:', this.taxi.image, 'Position:', this.taxi.x, this.taxi.y);
        this.ctx.drawImage(
          this.taxi.image,
          -this.taxi.width/2,
          -this.taxi.height/2,
          this.taxi.width,
          this.taxi.height
        );
      } else {
        // Fallback: Zeichne blaues Rechteck wenn Bild nicht geladen
        // console.log('Taxi-Bild nicht geladen, zeichne Fallback-Rechteck');
        this.ctx.fillStyle = '#2196F3';
        this.ctx.fillRect(-this.taxi.width/2, -this.taxi.height/2, this.taxi.width, this.taxi.height);
      }
      
      this.ctx.restore();
    },

    // Zeichne alle Autos
    drawCars() {
      this.cars.forEach(car => {
        this.ctx.save();
        this.ctx.translate(car.x + car.width/2, car.y + car.height/2);
        
        // Korrigiere die Rotation um 90° + weitere 180° - das Auto-Bild zeigt bereits in die richtige Richtung
        this.ctx.rotate(car.angle + Math.PI / 2 + Math.PI);
        
        if (this.carImage) {
          // Zeichne Auto-Bild
          this.ctx.drawImage(
            this.carImage,
            -car.width/2,
            -car.height/2,
            car.width,
            car.height
          );
        } else {
          // Fallback: Zeichne farbiges Rechteck wenn Bild nicht geladen
          this.ctx.fillStyle = car.color;
          this.ctx.fillRect(-car.width/2, -car.height/2, car.width, car.height);
          
          // Zeichne schwarze Umrandung
          this.ctx.strokeStyle = '#000000';
          this.ctx.lineWidth = 2;
          this.ctx.strokeRect(-car.width/2, -car.height/2, car.width, car.height);
        }
        
        this.ctx.restore();

        // Debug: permanenten Crash-Bereich (Hitbox) der Autos anzeigen
        const obb = this._buildObb(car, 2);
        this._drawDebugPoly(obb.corners, 'rgba(255,165,0,0.15)', '#ffa500');
      });
    },

    _drawDebugPoly(corners, fill, stroke) {
      if (!corners || corners.length < 3) return;
      const ctx = this.ctx;
      ctx.save();
      ctx.beginPath();
      ctx.moveTo(corners[0].x, corners[0].y);
      for (let i = 1; i < corners.length; i++) ctx.lineTo(corners[i].x, corners[i].y);
      ctx.closePath();
      if (fill) { ctx.fillStyle = fill; ctx.fill(); }
      if (stroke) { ctx.strokeStyle = stroke; ctx.lineWidth = 2; ctx.stroke(); }
      ctx.restore();
    },
    
    drawRoads() {
      const tileSize = this.tiles.size; // 400x400px
      
      // Zeichne nur das aktuelle Tile
      if (this.currentMap && this.currentMap.mapData) {
        const mapData = this.currentMap.mapData;
        const rows = mapData.length;
        const cols = mapData[0] ? mapData[0].length : 0;
        
        // Prüfe ob aktuelle Tile-Position gültig ist
        if (this.currentTile.row >= 0 && this.currentTile.row < rows && 
            this.currentTile.col >= 0 && this.currentTile.col < cols) {
          
          const tileType = mapData[this.currentTile.row][this.currentTile.col];
            if (tileType) {
              // Konvertiere Tile-Typ zu streetCoordinates.json Format
              const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
              
              // Zeichne Straßenregionen für das aktuelle Tile
              streetCoordinates.drawDriveableRegions(this.ctx, streetTileType, tileSize, 0, 0);
            
            // Zeichne Tile-Overlay falls verfügbar
            if (this.tiles.images[tileType]) {
              this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize);
            }
            // Häuser zeichnen (aus tileHouses)
            const absCol = this.currentTile.col + (this.currentMap.offsetX || 0);
            const absRow = this.currentTile.row + (this.currentMap.offsetY || 0);
            // Haltelinien (falls Ampel vorhanden) – 120px vom Rand, 5px dick, 40px breit
            if (this.getTrafficLightFor(absCol, absRow)) {
              const approaches = this.getTileApproaches(tileType);
              this.drawStopLinesOnMainCanvas(this.ctx, tileSize, approaches);
            }
            this.drawHousesOnMainCanvas(this.ctx, tileSize, absCol, absRow);
            // Straßennamen auf Haupt-Canvas zeichnen
            // Ampeln an jeder Ecke des Tiles (über den Häusern, unter den Namen)
            if (this.getTrafficLightFor(absCol, absRow)) {
              const phase = this.getTrafficLightPhase(absCol, absRow); // 0,1,2,3
              // Phase-Mapping je Ecke laut Vorgabe:
              // 0: H grün / V rot
              // 1: H gelb / V rot-gelb
              // 2: H rot / V grün
              // 3: H rot-gelb / V gelb
              const imgGreen = this.tiles.images['trafficlight-green'];
              const imgYellow = this.tiles.images['trafficlight-yellow'];
              const imgRed = this.tiles.images['trafficlight-red'];
              const imgRedYellow = this.tiles.images['trafficlight-redyellow'];
              {
                const sTL = tileSize * 0.16; // um 20% kleiner als zuvor
                // Positionierung nach Vorgabe:
                // X: 180px vom linken bzw. rechten Rand, dann je 60px zum näheren Rand und 5px zurück
                let leftX = 180;
                let rightX = tileSize - 180 - sTL;
                leftX = Math.max(0, leftX - 60);
                rightX = Math.min(tileSize - sTL, rightX + 60);
                leftX = Math.min(tileSize - sTL, Math.max(0, leftX + 5));
                rightX = Math.min(tileSize - sTL, Math.max(0, rightX - 5));
                // Y: oben korrekt, unten 60px weiter nach oben
                const topY = 180 - sTL;
                const bottomY = tileSize - 180 - 60;
                // Sichtbarkeit pro Seite bestimmen und auf Ecken mappen
                const ap = this.getTileApproaches(tileType);
                let showTL = false, showTR = false, showBL = false, showBR = false;
                if (ap.top) { showTL = true; showTR = true; }
                if (ap.bottom) { showBL = true; showBR = true; }
                if (ap.left) { showTL = true; showBL = true; }
                if (ap.right) { showTR = true; showBR = true; }
                // Tile-spezifische Ausnahmen (z.B. T-Kreuzungen)
                const hide = this.getCornerHidesForTile(tileType);
                if (hide.TL) showTL = false;
                if (hide.TR) showTR = false;
                if (hide.BL) showBL = false;
                if (hide.BR) showBR = false;
              const drawByAxis = (axis, x, y) => {
                // axis: 'H' (horizontal) oder 'V' (vertikal)
                let img = imgRed;
                if (axis === 'H') {
                  // Horizontal: Phase 0=grün, 1=gelb, 2=rot, 3=rot-gelb
                  if (phase === 0) img = imgGreen; else if (phase === 1) img = imgYellow; else if (phase === 2) img = imgRed; else img = imgRedYellow;
                } else {
                  // Vertikal: Gegenphase: 0=rot, 1=rot-gelb, 2=grün, 3=gelb
                  if (phase === 0) img = imgRed; else if (phase === 1) img = imgRedYellow; else if (phase === 2) img = imgGreen; else img = imgYellow;
                }
                if (img && img.complete) this.ctx.drawImage(img, x, y, sTL, sTL);
              };
              // Diagonale Synchronisation: TL/BR vertikal, TR/BL horizontal
              if (showTL) drawByAxis('V', leftX, topY);
              if (showTR) drawByAxis('H', rightX, topY);
              if (showBL) drawByAxis('H', leftX, bottomY);
              if (showBR) drawByAxis('V', rightX, bottomY);
              }
            }
            this.drawStreetNamesOnMainCanvas(this.ctx, tileSize, tileType, absCol, absRow);
          }
        }
      }
      else {
        // Fallback: Zeichne Standard-Tile wenn keine Map geladen ist
        const tileType = 'cornertopleft'; // Standard-Tile
        const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
        
        // Zeichne Straßenregionen
        streetCoordinates.drawDriveableRegions(this.ctx, streetTileType, tileSize, 0, 0);
        
        // Zeichne Tile-Overlay falls verfügbar
        if (this.tiles.images[tileType]) {
          this.ctx.drawImage(this.tiles.images[tileType], 0, 0, tileSize, tileSize);
        }
      }
    },
    getTrafficLightFor(col, row) {
      // Prüfe Tiles-Array, ob trafficLight-Spalte oder meta.trafficLight gesetzt ist
      if (!this.currentMap || !Array.isArray(this.currentMap.tiles)) return false;
      const found = this.currentMap.tiles.find(t => t.x === col && t.y === row && (t.trafficLight === true || (t.meta && t.meta.trafficLight)));
      return !!found;
    },
    getTrafficLightPhase(col, row) {
      const key = `${col},${row}`;
      const st = this.trafficLightStates[key];
      if (!st) return 0; // default
      return st.phase || 0;
    },

    drawStreetNamesOnMainCanvas(ctx, size, tileType, col, row) {
      if (!this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
      const entry = this.currentMap.tileStreets.find(ts => ts.x === col && ts.y === row);
      if (!entry) return;

      const fontPx = 12;
      const drawText = (text, px, py, rot = 0, selected = false) => {
        ctx.save();
        ctx.translate(px, py);
        if (rot) ctx.rotate(rot);
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.font = `bold ${fontPx}px sans-serif`;
        ctx.lineWidth = Math.max(2, Math.floor(fontPx / 6));
        ctx.strokeStyle = 'rgba(255,255,255,0.95)';
        ctx.strokeText(text, 0, 0);
        ctx.fillStyle = selected ? '#ff4d4d' : '#222';
        ctx.fillText(text, 0, 0);
        ctx.restore();
      };

      const hName = entry.streetNameH?.name || null;
      const vName = entry.streetNameV?.name || null;

      if (vName) {
        const sel = this.selectedStreetName && this.selectedStreetName === vName;
        const cx = size / 2;
        drawText(vName, cx, fontPx +1, 0, sel);
        drawText(vName, cx, size - fontPx -1, 0, sel);
      }

      if (hName) {
        const sel = this.selectedStreetName && this.selectedStreetName === hName;
        const cy = size / 2;
        drawText(hName, fontPx, cy, -Math.PI / 2, sel);
        drawText(hName, size - fontPx, cy, -Math.PI / 2, sel);
      }
    },
    drawHousesOnMainCanvas(ctx, size, col, row) {
      if (!this.currentMap) return;
      const houses = Array.isArray(this.currentMap.tileHouses) ? this.currentMap.tileHouses : [];
      if (!houses.length || !this.houseImage) return;
      // Finde alle Häuser für diese Zelle
      const list = houses.filter(h => h.x === col && h.y === row);
      if (!list.length) return;
      // Hausgröße 150x150px; an Ecken platzieren mit Abstand 3px
      const HOUSE_W = 150;
      const HOUSE_H = 150;
      const pad = 30;
      for (const h of list) {
        const rot = (Number(h.rotation) || 0) % 360;
        const isPortrait = (rot % 180) !== 0; // 90 oder 270 Grad
        const w = isPortrait ? HOUSE_H : HOUSE_W;
        const hgt = isPortrait ? HOUSE_W : HOUSE_H;

        let px = 0, py = 0;
        switch (h.corner) {
          case 'lo': px = pad; py = pad; break;
          case 'ro': px = size - w - pad; py = pad; break;
          case 'lu': px = pad; py = size - hgt - pad; break;
          case 'ru': px = size - w - pad; py = size - hgt - pad; break;
          default: continue;
        }
        // Rotation: Bild zeigt Tür unten (0°). 90/180/270 drehen um Zentrum.
        const cx = px + w / 2;
        const cy = py + hgt / 2;
        ctx.save();
        ctx.translate(cx, cy);
        const rad = rot * Math.PI / 180;
        ctx.rotate(rad);
        ctx.drawImage(this.houseImage, -HOUSE_W/2, -HOUSE_H/2, HOUSE_W, HOUSE_H);
        // Hausnummer zeichnen (über dem Bild zentriert)
        const key = `${h.x},${h.y},${h.corner}`;
        const num = this.houseNumbers[key];
        if (num != null) {
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.font = 'bold 20px sans-serif';
          ctx.lineWidth = 4;
          ctx.strokeStyle = 'rgba(255,255,255,0.9)';
          ctx.strokeText(String(num), 0, 0);
          ctx.fillStyle = '#000';
          ctx.fillText(String(num), 0, 0);
        }
        
        // Passagier zeichnen (falls vorhanden)
        this.drawPassengerOnHouse(ctx, h, col, row);
        
        ctx.restore();
      }
    },

    drawPassengerOnHouse(ctx, house, col, row) {
      // Finde Passagier für dieses Haus
      const passenger = this.waitingPassengersList.find(p => 
        p.tileX === col && p.tileY === row && (p.houseIndex === house.corner || p.houseCorner === house.corner)
      );
      
      if (!passenger || !this.passengerImages[passenger.imageIndex]) {
        return; // Kein Passagier oder Bild nicht geladen
      }
      
      const passengerImg = this.passengerImages[passenger.imageIndex];
      const PASSENGER_SIZE = 40; // Größe des Passagiers
      const HOUSE_H = 150; // Haushöhe (muss mit drawHousesOnMainCanvas übereinstimmen)
      
      // Zeichne Passagier auf dem Boden vor dem Haus (stehend)
      // Der untere Rand der Figur soll mit dem unteren Rand des Hauses ausgerichtet sein
      ctx.drawImage(
        passengerImg, 
        -PASSENGER_SIZE/2, 
        HOUSE_H/2 - PASSENGER_SIZE, // Unterer Rand der Figur am unteren Rand des Hauses
        PASSENGER_SIZE, 
        PASSENGER_SIZE
      );
    },

    // Haltelinien: Basierend auf den korrigierten Koordinaten
    drawStopLinesOnMainCanvas(ctx, size, approaches = { top:true, right:true, bottom:true, left:true }) {
      const thickness = 5;
      ctx.save();
      ctx.fillStyle = '#ffffff';
      
      // Basierend auf den korrigierten Koordinaten:
      // Links: sichtbar: 145/294 - 150/313
      if (approaches.left) ctx.fillRect(145, 294, thickness, 19);
      
      // Rechts: sichtbar: 150/186 - 355/293
      if (approaches.right) ctx.fillRect(350, 186, thickness, 107);
      
      // Unten: sichtbar: 250/360 - 312/365
      if (approaches.bottom) ctx.fillRect(250, 360, 62, thickness);
      
      // Oben: berechnet aus den anderen Angaben (symmetrisch zu unten)
      if (approaches.top) ctx.fillRect(250, 5, 62, thickness);
      
      ctx.restore();
    },
    // Liefert die virtuellen (breit verdoppelten) Haltelinienrechtecke pro Seite, ohne zu zeichnen
    getVirtualStopLineRects(size, approaches = { top:true, right:true, bottom:true, left:true }) {
      const thickness = 5 * 2; // doppelte Breite für Erkennung
      const rects = {};
      
      // Basierend auf den korrigierten Koordinaten:
      // Links: virtuell: 145/186 - 150/313; sichtbar: 145/294 - 150/313
      if (approaches.left) rects.left = { x: 145, y: 186, width: thickness, height: 127 };
      
      // Rechts: virtuell: 350/186 - 355/313; sichtbar: 150/186 - 355/293
      if (approaches.right) rects.right = { x: 350, y: 186, width: thickness, height: 127 };
      
      // Unten: virtuell: 187/360- 312/365; sichtbar: 250/360 - 312/365
      if (approaches.bottom) rects.bottom = { x: 187, y: 360, width: 125, height: thickness };
      
      // Oben: berechnet aus den anderen Angaben (symmetrisch zu unten)
      if (approaches.top) rects.top = { x: 187, y: 5, width: 125, height: thickness };
      
      return rects;
    },
    // Liefert, von welchen Seiten eine Straße an dieses Tile anbindet
    getTileApproaches(tileType) {
      switch (tileType) {
        case 'horizontal': return { top:false, right:true, bottom:false, left:true };
        case 'vertical':   return { top:true, right:false, bottom:true, left:false };
        case 'cross':      return { top:true, right:true, bottom:true, left:true };
        case 'cornertopleft':     return { top:true, right:false, bottom:false, left:true };
        case 'cornertopright':    return { top:true, right:true, bottom:false, left:false };
        case 'cornerbottomleft':  return { top:false, right:false, bottom:true, left:true };
        case 'cornerbottomright': return { top:false, right:true, bottom:true, left:false };
        case 'tup':        return { top:true, right:true, bottom:false, left:true };   // T-oben: unten gesperrt
        case 'tdown':      return { top:false, right:true, bottom:true, left:true };  // T-unten: oben gesperrt
        case 'tleft':      return { top:true, right:false, bottom:true, left:true };  // T-links: rechts gesperrt
        case 'tright':     return { top:true, right:true, bottom:true, left:false };  // T-rechts: links gesperrt
        case 'fuelhorizontal': return { top:false, right:true, bottom:false, left:true };
        case 'fuelvertical':   return { top:true, right:false, bottom:true, left:false };
        default: return { top:true, right:true, bottom:true, left:true };
      }
    },
    // Ecken-spezifische Ausblendungen je Tiletyp (zusätzlich zu Seiten-Logik)
    getCornerHidesForTile(tileType) {
      // TL=oben links, TR=oben rechts, BL=unten links, BR=unten rechts
      switch (tileType) {
        case 'tdown':      return { TL: true, TR: false, BL: false, BR: false }; // oben gesperrt -> oben links weg
        case 'tup':        return { TL: false, TR: false, BL: false, BR: true }; // unten gesperrt -> unten rechts weg
        case 'tleft':      return { TL: false, TR: true, BL: false, BR: false }; // rechts gesperrt -> oben rechts weg
        case 'tright':     return { TL: false, TR: false, BL: true, BR: false }; // links gesperrt -> unten links weg
        default:           return { TL: false, TR: false, BL: false, BR: false };
      }
    },
    
    getTileType(row, col, rows, cols) {
      // Ecken
      if (row === 0 && col === 0) return 'cornertopleft';
      if (row === 0 && col === cols - 1) return 'cornertopright';
      if (row === rows - 1 && col === 0) return 'cornerbottomleft';
      if (row === rows - 1 && col === cols - 1) return 'cornerbottomright';
      
      // Ränder
      if (row === 0 || row === rows - 1) return 'horizontal';
      if (col === 0 || col === cols - 1) return 'vertical';
      
      // Innere Bereiche - zufällige Straßen
      const rand = Math.random();
      if (rand < 0.3) return 'cross';
      if (rand < 0.5) return 'horizontal';
      if (rand < 0.7) return 'vertical';
      if (rand < 0.8) return 'fuelhorizontal';
      if (rand < 0.9) return 'fuelvertical';
      
      return 'horizontal'; // Fallback
    },
    
    async handleCanvasClick(event) {
      // AudioContext bei erster Benutzerinteraktion initialisieren
      this.ensureAudioUnlockedInEvent();
      // Motor ggf. direkt starten (Klick ist User-Geste)
      if (this.motorSound && !this.motorSound.isPlaying) {
        this.motorSound.start();
      }
    },
    
    async handleKeyDown(event) {
      // Browser-Shortcuts (F-Tasten, Strg/Meta+R) passieren lassen
      const key = event.key;
      const isFunctionKey = /^F\d{1,2}$/.test(key);
      const isReloadShortcut = (event.ctrlKey || event.metaKey) && (key === 'r' || key === 'R');
      if (isFunctionKey || isReloadShortcut) {
        return; // nicht abfangen, Browser soll handeln
      }

      this.keys[key] = true;
      
      // AudioContext bei erster Benutzerinteraktion initialisieren
      this.ensureAudioUnlockedInEvent();
      
      // Motor nur starten wenn Spiel nicht pausiert ist
      if (!this.isPaused && (event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) {
        this.motorSound.start();
        // Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen
        const speedKmh = Math.max(5, this.taxi.speed * 5);
        const speedFactor = Math.min(speedKmh / 120, 1);
        const motorSpeed = 0.3 + (speedFactor * 0.3);
        const volume = 0.1 + (speedFactor * 0.4);
        this.motorSound.setSpeed(motorSpeed);
        this.motorSound.setVolume(volume);
      }
      
      event.preventDefault();
    },

    ensureAudioUnlockedInEvent() {
      // Nur einmal initialisieren
      if (this.audioContext && this.motorSound) {
        return; // Bereits initialisiert
      }
      
      // Muss synchron im Event-Handler laufen (ohne await), damit Policies greifen
      if (!this.audioContext) {
        this.audioContext = window.__TaxiAudioContext || new (window.AudioContext || window.webkitAudioContext)();
        window.__TaxiAudioContext = this.audioContext;
      }
      try {
        if (this.audioContext.state === 'suspended') {
          this.audioContext.resume();
        }
      } catch (e) {
        // ignore
      }
      if (!this.motorSound) {
        this.motorSound = window.__TaxiMotorSound;
        if (!this.motorSound) {
          const generator = new NoiseGenerator();
          this.motorSound = new MotorSound(this.audioContext, generator);
          // nicht awaiten, bleibt im gleichen Event-Frame
          this.motorSound.init();
          window.__TaxiMotorSound = this.motorSound;
        }
      }
    },
    
    handleKeyUp(event) {
      const key = event.key;
      const isFunctionKey = /^F\d{1,2}$/.test(key);
      const isReloadShortcut = (event.ctrlKey || event.metaKey) && (key === 'r' || key === 'R');
      if (isFunctionKey || isReloadShortcut) {
        return; // Browser-Shortcut – nichts am Spielzustand ändern
      }
      this.keys[key] = false;
    },
    
    togglePause() {
      this.isPaused = !this.isPaused;
      this.showPauseOverlay = this.isPaused;
      
      // Motorgeräusch stoppen wenn pausiert, starten wenn fortgesetzt
      if (this.isPaused) {
        if (this.motorSound && this.motorSound.isPlaying) {
          this.motorSound.stop();
        }
      }
      // Wenn fortgesetzt wird, startet der Motor automatisch bei der nächsten Beschleunigung
    },
    
    restartLevel() {
      this.score = 0;
      this.money = 0;
      this.passengersDelivered = 0;
      this.waitingPassengersList = [];
      this.loadedPassengersList = [];
      this.occupiedHouses.clear();
      this.fuel = 100;
      this.taxi.x = 250;
      this.taxi.y = 250;
      this.taxi.angle = 0;
      this.taxi.speed = 0;
      
      // Reset Spielzeit
      this.gameStartTime = null;
      
      // Cleanup bestehender Timeouts
      if (this.passengerGenerationTimeout) {
        clearTimeout(this.passengerGenerationTimeout);
        this.passengerGenerationTimeout = null;
      }
      if (this.crashDialogTimeout) {
        clearTimeout(this.crashDialogTimeout);
        this.crashDialogTimeout = null;
      }
      
      this.generateLevel();
      this.initializePassengerGeneration();
    },
    
    
    
    initializeMinimap() {
      this.minimapCanvas = this.$refs.minimapCanvas;
      this.minimapCtx = this.minimapCanvas.getContext('2d');
    },
    
    async loadTiles() {
      const tileNames = [
        'cornerbottomleft', 'cornerbottomright', 'cornertopleft', 'cornertopright',
        'cross', 'fuelhorizontal', 'fuelvertical', 'horizontal', 'vertical',
        'tleft', 'tright', 'tup', 'tdown'
      ];
      
      const mapTileNames = [
        'map-cornerbottomleft', 'map-cornerbottomright', 'map-cornertopleft', 'map-cornertopright',
        'map-cross', 'map-fuelhorizontal', 'map-fuelvertical', 'map-horizontal', 'map-vertical',
        'map-tleft', 'map-tright', 'map-tup', 'map-tdown'
      ];
      
      // Lade normale Tiles
      for (const tileName of tileNames) {
        const img = new Image();
        img.src = `/images/taxi/${tileName}.svg`;
        this.tiles.images[tileName] = img;
      }
      
      // Lade Map-Tiles
      for (const tileName of mapTileNames) {
        const img = new Image();
        img.src = `/images/taxi/${tileName}.svg`;
        this.tiles.images[tileName] = img;
      }

        // Lade Ampel-Icon (zentral, 50% Größe)
        const red = new Image();
        red.src = '/images/taxi/redlight.svg';
        this.tiles.images['redlight-svg'] = red;

        // Lade Trafficlight-Zustände (für großes Tile an den Ecken)
        const tlStates = ['trafficlight-red','trafficlight-yellow','trafficlight-redyellow','trafficlight-green'];
        for (const key of tlStates) {
          const img = new Image();
          img.src = `/images/taxi/${key}.svg`;
          this.tiles.images[key] = img;
        }
      // Radar-Icon
      const radar = new Image();
      radar.src = '/images/taxi/radar.svg';
      this.tiles.images['radar'] = radar;
    },
    
    loadTaxiImage() {
      const img = new Image();
      img.onload = () => {
        // console.log('Taxi-Bild erfolgreich geladen:', img);
        this.taxi.image = img;
      };
      img.onerror = (error) => {
        console.error('Fehler beim Laden des Taxi-Bildes:', error);
        console.log('Versuche Pfad:', '/images/taxi/taxi.svg');
      };
      img.src = '/images/taxi/taxi.svg';
      // console.log('Lade Taxi-Bild von:', '/images/taxi/taxi.svg');
    },
    loadHouseImage() {
      const img = new Image();
      img.onload = () => {
        this.houseImage = img;
      };
      img.onerror = () => {
        console.warn('Fehler beim Laden von house_small.png');
      };
      img.src = '/images/taxi/house_small.png';
    },

    loadPassengerImages() {
      // Lade alle 6 Passagier-Bilder (1-3 männlich, 4-6 weiblich)
      for (let i = 1; i <= 6; i++) {
        const img = new Image();
        img.onload = () => {
          this.passengerImages[i] = img;
        };
        img.onerror = () => {
          console.warn(`Fehler beim Laden von passenger${i}.png`);
        };
        img.src = `/images/taxi/passenger${i}.png`;
      }
    },

    loadCarImage() {
      const img = new Image();
      img.onload = () => {
        this.carImage = img;
      };
      img.onerror = () => {
        console.warn('Fehler beim Laden von car1.svg');
      };
      img.src = '/images/taxi/car1.svg';
    },
    
    async loadMaps() {
      try {
        const response = await apiClient.get('/api/taxi-maps/maps');
        this.maps = (response.data.data || []).map(m => this.buildMapDataFromTiles(m));
        
        // Verwende die erste verfügbare Map als Standard
        if (this.maps.length > 0) {
          this.currentMap = this.maps[0];
          this.selectedMapId = this.maps[0].id;
          
          // Canvas-Größe an geladene Map anpassen
          this.updateCanvasSize();
          
          // Minimap neu zeichnen nach dem Laden der Map
          this.$nextTick(() => {
            this.computeHouseNumbers();
            this.drawMinimap();
          });
        }
      } catch (error) {
        console.error('Fehler beim Laden der Maps:', error);
      }
    },
    
    onMapChange() {
      // Wechsle zur ausgewählten Map
      const selectedMap = this.maps.find(map => map.id === this.selectedMapId);
      if (selectedMap) {
        this.currentMap = this.buildMapDataFromTiles({ ...selectedMap });
        // console.log('Gewechselt zu Map:', selectedMap);
        
        // Canvas-Größe an neue Map anpassen
        this.updateCanvasSize();
        
        // Taxi in die Mitte des 400x400px Tiles positionieren
        this.taxi.x = 250 - this.taxi.width/2; // Zentriert in der Mitte
        this.taxi.y = 250 - this.taxi.height/2; // Zentriert in der Mitte
        this.taxi.angle = 0; // Fahrtrichtung nach oben
        this.taxi.speed = 0; // Geschwindigkeit zurücksetzen
        
        // Aktuelle Tile-Position zurücksetzen
        this.currentTile.row = 0;
        this.currentTile.col = 0;
        
        // Minimap neu zeichnen
        this.$nextTick(() => {
          this.computeHouseNumbers();
          this.drawMinimap();
        });
      }
    },
    
    drawMinimap() {
      if (!this.minimapCtx) return;
      
      const ctx = this.minimapCtx;
      const canvas = this.minimapCanvas;
      
      // Minimap löschen
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      
      // Hintergrund
      ctx.fillStyle = '#f0f0f0';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      
      // Zeichne Map-Tiles aus der geladenen Map
      if (this.currentMap && this.currentMap.mapData) {
        const mapData = this.currentMap.mapData;
        const rows = mapData.length;
        const cols = mapData[0] ? mapData[0].length : 0;
        
        // Berechne optimale Tile-Größe für quadratische Tiles
        const maxTiles = Math.max(rows, cols);
        const tileSize = Math.min(canvas.width / maxTiles, canvas.height / maxTiles);
        
        // Zentriere die Map im Canvas
        const offsetX = (canvas.width - cols * tileSize) / 2;
        const offsetY = (canvas.height - rows * tileSize) / 2;
        
        // Set, um pro Straßennamen nur eine Nummer zu zeichnen
        const drawnNames = new Set();
        
        for (let row = 0; row < rows; row++) {
          for (let col = 0; col < cols; col++) {
            const x = offsetX + col * tileSize;
            const y = offsetY + row * tileSize;
            
            const tileType = mapData[row][col];
            if (tileType) {
              const mapTileType = 'map-' + tileType;
              
              // Zeichne Map-Tile falls verfügbar
              if (this.tiles.images[mapTileType]) {
                ctx.drawImage(this.tiles.images[mapTileType], x, y, tileSize, tileSize);
              }

              // Straßensegment der ausgewählten Straße hervorheben
              const absCol = col + (this.currentMap.offsetX || 0);
              const absRow = row + (this.currentMap.offsetY || 0);
              this.drawStreetFillOnMinimap(ctx, x, y, tileSize, tileType, absCol, absRow);

              // Straßennamen-Nummern zeichnen, basierend auf tileStreets (nur einmal pro Name)
              this.drawStreetNumbersOnMinimap(ctx, x, y, tileSize, tileType, absCol, absRow, drawnNames);

              // Ampel im Minimap zentriert (50% Größe)
              if (this.getTrafficLightFor(absCol, absRow)) {
                const img = this.tiles.images['redlight-svg'];
                if (img && img.complete) {
                  const s = tileSize * 0.3; // 60% der bisherigen 50%-Größe
                  ctx.drawImage(img, x + (tileSize - s) / 2, y + (tileSize - s) / 2, s, s);
                }
              }
            }
          }
        }
      } else {
        // Fallback: Zeichne statische Map wenn keine Map geladen ist
        const cols = 8;
        const rows = 8;
        
        // Berechne optimale Tile-Größe für quadratische Tiles
        const maxTiles = Math.max(rows, cols);
        const tileSize = Math.min(canvas.width / maxTiles, canvas.height / maxTiles);
        
        // Zentriere die Map im Canvas
        const offsetX = (canvas.width - cols * tileSize) / 2;
        const offsetY = (canvas.height - rows * tileSize) / 2;
        
        for (let row = 0; row < rows; row++) {
          for (let col = 0; col < cols; col++) {
            const x = offsetX + col * tileSize;
            const y = offsetY + row * tileSize;
            
            let tileType = this.getTileType(row, col, rows, cols);
            let mapTileType = 'map-' + tileType;
            
            // Zeichne Map-Tile falls verfügbar
            if (this.tiles.images[mapTileType]) {
              ctx.drawImage(this.tiles.images[mapTileType], x, y, tileSize, tileSize);
            }

            // Keine echten Namen im Fallback
          }
        }
      }
      
         // Berechne Skalierung für Passagiere, Ziele und Tankstellen
         let scaleX, scaleY, offsetX, offsetY;
         if (this.currentMap && this.currentMap.mapData) {
           const mapData = this.currentMap.mapData;
           const rows = mapData.length;
           const cols = mapData[0] ? mapData[0].length : 0;
           const maxTiles = Math.max(rows, cols);
           const tileSize = Math.min(canvas.width / maxTiles, canvas.height / maxTiles);
           scaleX = tileSize / 500; // 400 ist die neue Tile-Größe
           scaleY = tileSize / 500;
           offsetX = (canvas.width - cols * tileSize) / 2;
           offsetY = (canvas.height - rows * tileSize) / 2;
         } else {
           // Fallback für statische Map
           const maxTiles = 8;
           const tileSize = Math.min(canvas.width / maxTiles, canvas.height / maxTiles);
           scaleX = tileSize / 500;
           scaleY = tileSize / 500;
           offsetX = (canvas.width - 8 * tileSize) / 2;
           offsetY = (canvas.height - 8 * tileSize) / 2;
         }
      
      
      
         // Taxi (blau) - Position basierend auf aktueller Tile-Position
         ctx.fillStyle = '#2196F3';
         ctx.beginPath();
         const taxiGlobalX = this.currentTile.col * 500 + this.taxi.x;
         const taxiGlobalY = this.currentTile.row * 500 + this.taxi.y;
         ctx.arc(offsetX + taxiGlobalX * scaleX, offsetY + taxiGlobalY * scaleY, 3, 0, 2 * Math.PI);
         ctx.fill();

         // Taxi-Richtung anzeigen
         ctx.strokeStyle = '#2196F3';
         ctx.lineWidth = 1;
         ctx.beginPath();
         const taxiX = offsetX + taxiGlobalX * scaleX;
         const taxiY = offsetY + taxiGlobalY * scaleY;
         const endX = taxiX + Math.cos(this.taxi.angle) * 6;
         const endY = taxiY + Math.sin(this.taxi.angle) * 6;
         ctx.moveTo(taxiX, taxiY);
         ctx.lineTo(endX, endY);
         ctx.stroke();
    },
    buildMapDataFromTiles(map) {
      if (!map || !Array.isArray(map.tiles) || map.tiles.length === 0) {
        map.mapData = null;
        map.offsetX = 0;
        map.offsetY = 0;
        return map;
      }
      const xs = map.tiles.map(t => t.x);
      const ys = map.tiles.map(t => t.y);
      const minX = Math.min(...xs);
      const maxX = Math.max(...xs);
      const minY = Math.min(...ys);
      const maxY = Math.max(...ys);
      const width = (maxX - minX + 1) || 1;
      const height = (maxY - minY + 1) || 1;
      const grid = Array.from({ length: height }, () => Array(width).fill(null));
      for (const t of map.tiles) {
        const col = t.x - minX;
        const row = t.y - minY;
        if (row >= 0 && row < height && col >= 0 && col < width) {
          grid[row][col] = t.tileType;
        }
      }
      map.mapData = grid;
      map.offsetX = minX;
      map.offsetY = minY;
      // tileHouses vom Backend übernehmen (Array von Zeilen)
      map.tileHouses = Array.isArray(map.tileHouses) ? map.tileHouses : [];
      return map;
    },
    onSelectStreet(name) {
      this.selectedStreetName = (this.selectedStreetName === name) ? null : name;
    },
    // Variante B: gesamte Straßenfläche im Tile halbtransparent rot füllen
    drawStreetFillOnMinimap(ctx, x, y, size, tileType, col, row) {
      if (!this.selectedStreetName || !this.currentMap || !Array.isArray(this.currentMap.tileStreets)) return;
      const entry = this.currentMap.tileStreets.find(ts => ts.x === col && ts.y === row);
      if (!entry) return;

      const wantH = entry.streetNameH?.name === this.selectedStreetName;
      const wantV = entry.streetNameV?.name === this.selectedStreetName;
      if (!wantH && !wantV) return;

      const streetTileType = this.mapTileTypeToStreetCoordinates(tileType);
      const regions = streetCoordinates.getDriveableRegions(streetTileType);
      if (!regions || regions.length === 0) return;

      // Banddicke exakt 12px (bei Referenz 50px) skaliert auf aktuelle size
      const scale = size / 50;
      const bandThickness = 12 * scale; // px

      // Clip: Tile-Rechteck MINUS Hindernis-Regionen => übrig bleibt die graue Straßenfläche
      ctx.save();
      ctx.beginPath();
      // 1) volles Tile
      ctx.rect(x, y, size, size);
      // 2) Hindernis-Regionen hinzufügen (EvenOdd)
      for (const region of regions) {
        if (!region || region.length === 0) continue;
        ctx.moveTo(x + region[0].x, y + region[0].y);
        for (let i = 1; i < region.length; i++) {
          ctx.lineTo(x + region[i].x, y + region[i].y);
        }
        ctx.closePath();
      }
      // EvenOdd erhält Fläche außerhalb der Regionen (Straße)
      ctx.clip('evenodd');

      ctx.fillStyle = '#ff4d4d';
      const marginRight = 1; // rechts 1px kürzen
      const marginBottom = 1; // unten 1px kürzen
      if (wantH) {
        const by = y + size * 0.5 - bandThickness / 2;
        ctx.fillRect(x, by, size, bandThickness);
      }
      if (wantV) {
        const bx = x + size * 0.5 - bandThickness / 2;
        ctx.fillRect(bx, y, bandThickness, size);
      }

      ctx.restore();
    },
    // ---- Hausnummern-Berechnung ----
    computeHouseNumbers() {
      this.houseNumbers = {};
      if (!this.currentMap || !this.currentMap.tileHouses || !this.currentMap.mapData) return;
      const map = this.currentMap;
      const offsetX = map.offsetX || 0;
      const offsetY = map.offsetY || 0;
      const streetIndex = new Map();
      const getEntry = (name) => { if (!streetIndex.has(name)) streetIndex.set(name, { tiles: new Map() }); return streetIndex.get(name); };
      const tileStreets = Array.isArray(map.tileStreets) ? map.tileStreets : [];
      for (const ts of tileStreets) {
        const x = ts.x; const y = ts.y; const gridX = x - offsetX; const gridY = y - offsetY;
        const tileType = (map.mapData[gridY] && map.mapData[gridY][gridX]) || null; if (!tileType) continue;
        const makeTileObj = (name) => { const key = `${x},${y}`; const e = getEntry(name); const t = e.tiles.get(key) || { x, y, tileType, h: false, v: false }; e.tiles.set(key, t); return t; };
        if (ts.streetNameH && ts.streetNameH.name) { makeTileObj(ts.streetNameH.name).h = true; }
        if (ts.streetNameV && ts.streetNameV.name) { makeTileObj(ts.streetNameV.name).v = true; }
      }
      const getTileDirectionsLocal = (tileType) => {
        // Verwende die gleiche Logik wie getAllowedSpawnDirections, aber im alten Format
        switch (tileType) {
          case 'cornertopleft': return { left: true, up: true, right: false, down: false };
          case 'cornertopright': return { right: true, up: true, left: false, down: false };
          case 'cornerbottomleft': return { left: true, down: true, right: false, up: false };
          case 'cornerbottomright': return { right: true, down: true, left: false, up: false };
          case 'horizontal':
          case 'fuelhorizontal': return { left: true, right: true, up: false, down: false };
          case 'vertical':
          case 'fuelvertical': return { up: true, down: true, left: false, right: false };
          case 'cross': return { left: true, right: true, up: true, down: true };
          case 'tup': return { up: true, left: true, right: true, down: false };
          case 'tdown': return { down: true, left: true, right: true, up: false };
          case 'tleft': return { left: true, up: true, down: true, right: false };
          case 'tright': return { right: true, up: true, down: true, left: false };
          default: return { left: false, right: false, up: false, down: false };
        }
      };
      const getTile = (entry, x, y) => entry.tiles.get(`${x},${y}`);
      for (const [name, entry] of streetIndex.entries()) {
        const nodes = new Map();
        const addNode = (x, y, axis) => nodes.set(`${x},${y},${axis}`, { x, y, axis });
        for (const t of entry.tiles.values()) { if (t.h) addNode(t.x, t.y, 'h'); if (t.v) addNode(t.x, t.y, 'v'); }
        const adj = new Map();
        const addEdge = (a, b) => { if (!nodes.has(a) || !nodes.has(b)) return; if (!adj.has(a)) adj.set(a, new Set()); if (!adj.has(b)) adj.set(b, new Set()); adj.get(a).add(b); adj.get(b).add(a); };
        for (const t of entry.tiles.values()) {
          const dirs = getTileDirectionsLocal(t.tileType); const keyH = `${t.x},${t.y},h`; const keyV = `${t.x},${t.y},v`;
          if (t.h) { if (dirs.left) { const n = getTile(entry, t.x - 1, t.y); if (n && n.h && getTileDirectionsLocal(n.tileType).right) addEdge(keyH, `${n.x},${n.y},h`); } if (dirs.right) { const n = getTile(entry, t.x + 1, t.y); if (n && n.h && getTileDirectionsLocal(n.tileType).left) addEdge(keyH, `${n.x},${n.y},h`); } }
          if (t.v) { if (dirs.up) { const n = getTile(entry, t.x, t.y - 1); if (n && n.v && getTileDirectionsLocal(n.tileType).down) addEdge(keyV, `${n.x},${n.y},v`); } if (dirs.down) { const n = getTile(entry, t.x, t.y + 1); if (n && n.v && getTileDirectionsLocal(n.tileType).up) addEdge(keyV, `${n.x},${n.y},v`); } }
          if (t.h && t.v) { addEdge(keyH, keyV); }
        }
        if (nodes.size === 0) continue;
        // Startknoten deterministisch wählen:
        // 1) bevorzuge Endpunkte (Grad=1)
        // 2) bevorzuge horizontale Segmente (axis==='h') und wähle den mit kleinstem x (links)
        // 3) sonst vertikale Segmente (axis==='v') mit kleinstem y (oben)
        const nodeEntries = Array.from(nodes.entries());
        const deg = (k) => (adj.get(k)?.size || 0);
        let candidates = nodeEntries.filter(([k]) => deg(k) === 1);
        if (candidates.length === 0) candidates = nodeEntries;
        let horiz = candidates.filter(([, n]) => n.axis === 'h');
        let startPair = null;
        if (horiz.length > 0) {
          // Links-vorne nimmt Vorrang (kleinstes x, dann kleinstes y)
          startPair = horiz.reduce((best, curr) => {
            if (!best) return curr;
            const bn = best[1], cn = curr[1];
            if (cn.x < bn.x) return curr;
            if (cn.x === bn.x && cn.y < bn.y) return curr;
            return best;
          }, null);
        } else {
          const vert = candidates.filter(([, n]) => n.axis === 'v');
          startPair = vert.reduce((best, curr) => {
            if (!best) return curr;
            const bn = best[1], cn = curr[1];
            if (cn.y < bn.y) return curr;
            if (cn.y === bn.y && cn.x < bn.x) return curr;
            return best;
          }, null) || candidates[0];
        }
        let startKey = startPair ? startPair[0] : (nodeEntries[0] && nodeEntries[0][0]);
        const visited = new Set(); let odd = 1, even = 2; let prev = null; let curr = startKey; let currOddSide = (nodes.get(curr).axis === 'h') ? 'top' : 'left';
        while (curr) {
          visited.add(curr);
          const { x, y, axis } = nodes.get(curr);
          const neighbors = Array.from(adj.get(curr) || []); const next = neighbors.find(n => !visited.has(n));
          let dir = { dx: 0, dy: 0 }; if (next) { const nNode = nodes.get(next); dir = { dx: nNode.x - x, dy: nNode.y - y }; } else if (prev) { const pNode = nodes.get(prev); dir = { dx: x - pNode.x, dy: y - pNode.y }; }
          if (prev) { const prevAxis = nodes.get(prev).axis; if (prevAxis !== axis) { if (prevAxis === 'v' && axis === 'h') currOddSide = 'bottom'; else if (prevAxis === 'h' && axis === 'v') currOddSide = 'left'; } }
          const houseMap = this.currentMap.tileHouses || []; const housesHere = houseMap.filter(hh => hh.x === x && hh.y === y);
          if (housesHere.length) {
            const doorSideFromRotation = (rot) => {
              const r = ((Number(rot) || 0) % 360 + 360) % 360;
              if (r === 0) return 'bottom';
              if (r === 90) return 'left';
              if (r === 180) return 'top';
              if (r === 270) return 'right';
              return 'bottom';
            };
            // Nur Häuser berücksichtigen, deren Tür zur aktuellen Straßenachse passt
            const housesFiltered = housesHere.filter(hh => {
              const side = doorSideFromRotation(hh.rotation);
              return axis === 'h' ? (side === 'top' || side === 'bottom') : (side === 'left' || side === 'right');
            });
            if (!housesFiltered.length) { prev = curr; curr = next || null; continue; }
            const orderCorners = (list) => {
              if (axis === 'h') {
                // Erzwinge Nummerierung beginnend von links nach rechts
                const seq = ['lo','ro','lu','ru'];
                return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b));
              } else {
                const movingDown = dir.dy > 0 || (dir.dy === 0 && !prev);
                const seq = movingDown ? ['lo','lu','ro','ru'] : ['lu','lo','ru','ro'];
                return list.slice().sort((a,b)=> seq.indexOf(a) - seq.indexOf(b));
              }
            };
            const oddCorners = (axis === 'h') ? (currOddSide === 'top' ? ['lo','ro'] : ['lu','ru']) : (currOddSide === 'left' ? ['lo','lu'] : ['ro','ru']);
            const evenCorners = (axis === 'h') ? (currOddSide === 'top' ? ['lu','ru'] : ['lo','ro']) : (currOddSide === 'left' ? ['ro','ru'] : ['lo','lu']);
            for (const c of orderCorners(oddCorners)) { if (housesFiltered.find(hh => hh.corner === c)) { const k = `${x},${y},${c}`; if (this.houseNumbers[k] == null) { this.houseNumbers[k] = odd; odd += 2; } } }
            for (const c of orderCorners(evenCorners)) { if (housesFiltered.find(hh => hh.corner === c)) { const k = `${x},${y},${c}`; if (this.houseNumbers[k] == null) { this.houseNumbers[k] = even; even += 2; } } }
          }
          prev = curr; curr = next || null;
        }
      }
    },
    
    // Highscore-Funktionen
    async toggleHighscore() {
      this.showHighscore = !this.showHighscore;
      
      if (this.showHighscore) {
        // Spiel pausieren wenn Highscore angezeigt wird
        if (!this.isPaused) {
          this.isPaused = true;
          
          // Game Loop stoppen
          if (this.gameLoop) {
            cancelAnimationFrame(this.gameLoop);
            this.gameLoop = null;
          }
          
          // Motorgeräusch stoppen wenn pausiert
          if (this.motorSound && this.motorSound.isPlaying) {
            this.motorSound.stop();
          }
        }
        // Highscore laden
        await this.loadHighscore();
      } else {
        // Highscore geschlossen - Spiel automatisch fortsetzen
        this.isPaused = false;
        this.showPauseOverlay = false;
        
        // Game Loop neu starten
        if (this.gameRunning && !this.gameLoop) {
          this.gameLoop = requestAnimationFrame(this.update);
        }
        
        // Motor startet automatisch bei der nächsten Beschleunigung
      }
    },
    
    async loadHighscore() {
      this.loadingHighscore = true;
      try {
        // Lade Top 20 Highscores für die aktuelle Map
        const response = await apiClient.get('/api/taxi/highscores', {
          params: {
            mapId: this.selectedMapId,
            limit: 20,
            orderBy: 'points' // Sortiere nach Punkten
          }
        });
        
        if (response.data && response.data.success && Array.isArray(response.data.data)) {
          this.highscoreList = response.data.data.map((entry, index) => ({
            rank: index + 1,
            nickname: entry.nickname || 'Unbekannt',
            points: entry.points,
            isCurrentPlayer: entry.userId === this.$store.state.user?.id
          }));
          
          // Prüfe ob aktueller Spieler eine Platzierung hat
          await this.checkCurrentPlayerRank();
        }
      } catch (error) {
        console.error('Fehler beim Laden der Highscores:', error);
        this.highscoreList = [];
      } finally {
        this.loadingHighscore = false;
      }
    },
    
    async checkCurrentPlayerRank() {
      if (!this.$store.state.user?.id) return;
      
      try {
        // Lade Rang des aktuellen Spielers
        const response = await apiClient.get('/api/taxi/highscores/rank', {
          params: {
            userId: this.$store.state.user.id,
            mapId: this.selectedMapId,
            orderBy: 'points'
          }
        });
        
        if (response.data && response.data.success && response.data.data && response.data.data.rank) {
          const rank = response.data.data.rank;
          
          // Wenn Spieler Platz 21 oder schlechter hat
          if (rank > 20) {
            this.showCurrentPlayerBelow = true;
            
            // Lade beste Punkte des Spielers
            const bestScoreResponse = await apiClient.get('/api/taxi/highscores/user/best', {
              params: {
                userId: this.$store.state.user.id,
                mapId: this.selectedMapId
              }
            });
            
            if (bestScoreResponse.data && bestScoreResponse.data.success && bestScoreResponse.data.data) {
              this.currentPlayerEntry = {
                rank: rank,
                nickname: this.$store.state.user.nickname || 'Du',
                points: bestScoreResponse.data.data.points,
                isCurrentPlayer: true
              };
            }
          } else {
            this.showCurrentPlayerBelow = false;
            this.currentPlayerEntry = null;
          }
        }
      } catch (error) {
        console.error('Fehler beim Laden des Spieler-Rangs:', error);
      }
    }
  }
}
</script>

<style scoped>
/* Verwendet globale Scroll-Klassen: .contenthidden und .contentscroll */

.game-title {
  text-align: center;
  margin-bottom: 30px;
  padding-top: 20px;
}

.game-title h1 {
  margin: 0 0 10px 0;
  font-size: 2rem;
  font-weight: 600;
  color: #333;
}

.game-title p {
  margin: 0;
  font-size: 1.1rem;
  color: #666;
  line-height: 1.5;
}

/* Spiel-Layout */
.game-layout {
  display: flex;
  flex-direction: row;
  gap: 20px;
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 20px;
  align-items: flex-start;
}

/* Game Canvas Section */
.game-canvas-section {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 500px;
  margin: 0 auto; /* Zentriert den gesamten Container */
}

/* Tacho-Display */
.tacho-display {
  width: 500px; /* Gleiche Breite wie das zentrale Tile */
  background: #f8f9fa;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 8px 12px;
  margin: 0; /* Kein Margin */
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  box-sizing: border-box; /* Padding wird in die Breite eingerechnet */
}

.tacho-speed {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 6px;
  font-size: 10pt;
  color: #333;
  font-weight: 500;
}

.redlight-counter { display: inline-flex; align-items: center; gap: 4px; margin-right: 8px; }
.redlight-icon { font-size: 10pt; }
.redlight-value { font-weight: 700; min-width: 16px; text-align: right; }
.speed-group { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.redlight-counter { white-space: nowrap; }

.tacho-icon {
  font-size: 10pt;
}

.lives { font-weight: 700; color: #d60000; }
.fuel { font-weight: 600; color: #0a7c00; margin-left: 8px; }
.score { font-weight: 700; color: #ffa500; margin-left: 8px; }
.speed-violations { font-weight: 700; color: #8a2be2; margin-left: 8px; }

.speed-value {
  font-weight: bold;
  color: #2196F3;
}

.speed-unit {
  color: #666;
  font-size: 9pt;
}

.game-board-section {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0; /* Verhindert Overflow */
}

.game-area {
  display: flex;
  flex-direction: row;
  gap: 20px;
  align-items: flex-start;
  width: 100%;
  max-width: 1200px;
  justify-content: center; /* Zentriert den Inhalt */
}

.sidebar-section {
  width: 320px;
  display: flex;
  flex-direction: column;
  gap: 20px;
  position: sticky;
  top: 20px;
}

/* Loaded Passengers Card */
.loaded-passengers-card {
  background: #e8f5e8;
  border: 1px solid #4caf50;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
  margin-bottom: 20px;
}

.loaded-passengers-header {
  background: #4caf50;
  border-bottom: 1px solid #4caf50;
  padding: 5px 20px;
}

.loaded-passengers-title {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: #fff;
}

.loaded-passengers-content {
  padding: 5px 20px;
  max-height: 200px;
  overflow-y: auto;
}

.passengers-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.passengers-table th {
  background: #4caf50;
  color: white;
  padding: 8px 6px;
  text-align: left;
  font-weight: 600;
  font-size: 0.8rem;
}

.passengers-table th:first-child {
  width: 25%;
}

.passengers-table th:nth-child(2) {
  width: 35%;
}

.passengers-table th:nth-child(3) {
  width: 20%;
}

.passengers-table th:last-child {
  width: 20%;
}

.passenger-row {
  border-bottom: 1px solid #e0e0e0;
}

.passenger-row:last-child {
  border-bottom: none;
}

.passenger-row:hover {
  background: #f5f5f5;
}

.passenger-name-cell {
  padding: 6px;
  font-weight: 500;
  color: #333;
}

.passenger-destination-cell {
  padding: 6px;
  color: #2e7d32;
  font-size: 0.8rem;
}

.passenger-bonus-cell {
  padding: 6px;
  color: #ff9800;
  font-weight: 600;
  text-align: center;
  font-size: 0.8rem;
}

.passenger-time-cell {
  padding: 6px;
  text-align: center;
  font-weight: 600;
  font-size: 0.8rem;
}

.time-warning {
  background: #fff3cd !important;
  color: #856404 !important;
}

.time-critical {
  background: #f8d7da !important;
  color: #721c24 !important;
  animation: blink 1s infinite;
}

@keyframes blink {
  0%, 50% { opacity: 1; }
  51%, 100% { opacity: 0.5; }
}

/* Waiting Passengers Card */
.waiting-passengers-card {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
  margin-bottom: 20px;
}

.waiting-passengers-header {
  background: #f8f9fa;
  border-bottom: 1px solid #ddd;
  padding: 5px 20px;
}

.waiting-passengers-title {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: #333;
}

.waiting-passengers-list {
  padding: 5px 20px;
  max-height: 300px;
  overflow-y: auto;
}

.no-passengers {
  text-align: center;
  color: #666;
  font-style: italic;
  padding: 5px 0;
}

.passenger-item {
  padding: 10px 0;
  border-bottom: 1px solid #f0f0f0;
}

.passenger-item:last-child {
  border-bottom: none;
}

.passenger-item.loaded {
  background: #f0f8f0;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid #4caf50;
  margin-bottom: 8px;
}

.passenger-item.loaded:last-child {
  margin-bottom: 0;
}

.passenger-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.passenger-name {
  font-weight: 600;
  color: #333;
  font-size: 0.95rem;
}

.passenger-location {
  font-size: 0.85rem;
  color: #666;
}

.passenger-destination {
  font-size: 0.85rem;
  color: #2e7d32;
  font-weight: 500;
}

.passenger-timer {
  font-size: 0.8rem;
  color: #ff5722;
  font-weight: bold;
  margin-left: 4px;
}

/* Minimap Card */
.minimap-card {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}

.minimap-header {
  background: #f8f9fa;
  border-bottom: 1px solid #ddd;
  padding: 5px 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.minimap-title {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 600;
  color: #333;
}

.map-selector {
  flex: 0 0 auto;
}

.map-select {
  padding: 5px 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 0.9rem;
  background: white;
  cursor: pointer;
}

.map-select:focus {
  outline: none;
  border-color: #F9A22C;
}

.minimap-container {
  padding: 15px;
  text-align: center;
}

.minimap-canvas {
  border: 1px solid #ddd;
  border-radius: 4px;
  background: #f9f9f9;
  max-width: 100%;
  height: auto;
}

/* Controls Legend */
.controls-legend {
  background: #fff;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  width: 200px;
  flex-shrink: 0;
}

.controls-legend h3 {
  margin: 0 0 15px 0;
  color: #333;
  font-size: 1.1rem;
  text-align: center;
}

.legend-grid {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.legend-key {
  background: #f0f0f0;
  border: 1px solid #ddd;
  border-radius: 4px;
  padding: 4px 8px;
  font-weight: bold;
  font-size: 0.8rem;
  min-width: 40px;
  text-align: center;
  color: #333;
}

.legend-text {
  color: #666;
  font-size: 0.9rem;
  flex: 1;
}

/* Game Objectives in Legend */
.controls-legend .game-objectives {
  margin-top: 20px;
  padding-top: 15px;
  border-top: 1px solid #eee;
}

.controls-legend .game-objectives h4 {
  margin: 0 0 10px 0;
  color: #333;
  font-size: 1rem;
}

.controls-legend .game-objectives ul {
  margin: 0;
  padding-left: 15px;
  color: #666;
  font-size: 0.85rem;
  line-height: 1.4;
}

.controls-legend .game-objectives li {
  margin-bottom: 4px;
}

/* Game Canvas */
.game-canvas-container {
  margin: 0; /* Kein Margin */
  text-align: center;
  width: 500px; /* Feste Breite wie das Tacho-Display */
  height: 500px; /* Feste Höhe beibehalten */
  box-sizing: border-box; /* Border wird in die Breite eingerechnet */
}

.game-canvas {
  border: 2px solid #ddd;
  border-radius: 8px;
  background: #f0f0f0;
  cursor: crosshair;
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}

/* Pause Overlay */
.pause-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.pause-message {
  background: white;
  padding: 40px;
  border-radius: 12px;
  text-align: center;
  box-shadow: 0 8px 16px rgba(0,0,0,0.3);
}

.pause-message h2 {
  margin: 0 0 20px 0;
  color: #333;
  font-size: 1.5rem;
}

.resume-button {
  background: #4CAF50;
  color: white;
  border: none;
  padding: 12px 24px;
  border-radius: 6px;
  font-size: 1rem;
  cursor: pointer;
  transition: background 0.3s;
}

.resume-button:hover {
  background: #45a049;
}

/* Game Controls */
.game-controls {
  display: flex;
  gap: 10px;
  margin: 20px 0;
  flex-wrap: wrap;
  justify-content: center;
}

.control-button {
  background: #F9A22C;
  color: #000;
  border: 1px solid #F9A22C;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
  transition: all 0.3s;
}

.control-button:hover {
  background: #fdf1db;
  color: #7E471B;
  border: 1px solid #7E471B;
}

/* Highscore Overlay */
.highscore-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 500px;
  height: 500px;
  background: rgba(255, 255, 255, 0.95);
  backdrop-filter: blur(5px);
  border: 2px solid #ddd;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  z-index: 10;
}

.highscore-header {
  background: #F9A22C;
  color: #000;
  padding: 20px;
  text-align: center;
  border-bottom: 2px solid #ddd;
}

.highscore-header h2 {
  margin: 0 0 5px 0;
  font-size: 1.5rem;
  font-weight: 600;
}

.highscore-subtitle {
  margin: 0;
  font-size: 0.9rem;
  opacity: 0.8;
}

.highscore-list {
  flex: 1;
  overflow-y: auto;
  padding: 15px;
}

.loading-message,
.no-highscore {
  text-align: center;
  padding: 40px 20px;
  color: #666;
  font-style: italic;
}

.highscore-table {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.highscore-entry {
  display: grid;
  grid-template-columns: 50px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 10px 15px;
  background: #f8f9fa;
  border-radius: 4px;
  transition: all 0.2s;
}

.highscore-entry:hover {
  background: #e9ecef;
}

.highscore-entry.current-player {
  background: #fff3cd;
  border: 2px solid #ffc107;
  font-weight: 600;
}

.highscore-entry.current-player:hover {
  background: #ffe8a1;
}

.highscore-rank {
  font-size: 1.2rem;
  font-weight: 700;
  color: #F9A22C;
  text-align: center;
}

.highscore-name {
  font-size: 1rem;
  color: #333;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.highscore-points {
  font-size: 1rem;
  font-weight: 600;
  color: #666;
  text-align: right;
}

.highscore-separator {
  text-align: center;
  font-size: 1.5rem;
  font-weight: 700;
  color: #999;
  padding: 10px 0;
  letter-spacing: 5px;
}


/* Responsive Design */
@media (max-width: 1024px) {
  .game-layout {
    flex-direction: column;
  }
  
  .sidebar-section {
    width: 100%;
    position: static;
    order: -1; /* Sidebar kommt vor dem Spielbrett */
  }
  
  .game-board-section {
    width: 100%;
  }
  
  .game-area {
    flex-direction: column;
    align-items: center;
  }
  
  .controls-legend {
    width: 100%;
    max-width: 500px;
  }
}

@media (max-width: 768px) {
  .game-canvas {
    width: 100%;
    height: auto;
    max-width: 100%;
  }
  
  .instructions-grid {
    grid-template-columns: 1fr;
  }
  
  .game-controls {
    flex-direction: column;
    align-items: center;
  }
  
  .control-button {
    width: 200px;
  }
  
  .minimap-canvas {
    width: 100%;
    height: auto;
  }
}

.legend-street-item {
  cursor: pointer;
}
.legend-street-item.selected {
  font-weight: 700;
}
</style>
