fetchOne( 'SELECT class_id, display_name FROM student_sessions WHERE id = ?', [$sessionId] ); if ($row) { $studentName = $row['display_name'] ?: 'Schüler*in'; $classId = (int)$row['class_id']; // Aktiver, noch nicht erledigter Lehrer-Auftrag? Dann frisch starten, // damit der Schüler nicht aus alter Free-Play-Session resumed. if ($classId) { $cm = $db->fetchOne( 'SELECT mode, started_at, current_level FROM class_modules WHERE class_id = ? AND module_id = ?', [$classId, 'klima'] ); if ($cm && $cm['mode'] === 'teacher_started' && $cm['started_at']) { $done = $db->fetchOne( 'SELECT 1 FROM assessments WHERE session_id = ? AND sim_id = ? AND submitted_at > ? LIMIT 1', [$sessionId, 'klima', $cm['started_at']] ); if (!$done) $skipResume = true; $level = (int)($cm['current_level'] ?? $level); if ($level < 1 || $level > 3) $level = 1; } } } } // Modul-Info aus DB (Atlas-verwaltet). Defaults greifen, wenn Tabelle // fehlt oder kein Eintrag für "klima" existiert. $mi = $db->fetchOne( 'SELECT title, icon FROM module_info WHERE module_id = ?', ['klima'] ); if ($mi) { if (!empty($mi['title'])) $simName = $mi['title']; if (!empty($mi['icon'])) $simIcon = $mi['icon']; } } catch (Throwable $e) { // DB nicht erreichbar → Defaults } // Level-Konfiguration — fachlich passend zur Spiellogik (V1). // easy = Lernen, medium = Üben, hard = Profi $levelConfigs = [ 1 => [ 'difficulty' => 'easy', 'label' => 'Lernen', 'startBudget' => 600, 'startPopulation'=> 6000, 'incomePer10k' => 220, 'blackoutGrace' => 5, 'timeLimit' => 75, 'eventFrequency' => 0.3, ], 2 => [ 'difficulty' => 'medium', 'label' => 'Üben', 'startBudget' => 480, 'startPopulation'=> 6000, 'incomePer10k' => 155, 'blackoutGrace' => 0, 'timeLimit' => 75, 'eventFrequency' => 0.5, ], 3 => [ 'difficulty' => 'hard', 'label' => 'Profi', 'startBudget' => 220, 'startPopulation'=> 7000, 'incomePer10k' => 110, 'blackoutGrace' => 0, 'timeLimit' => 75, 'eventFrequency' => 0.7, ], ]; $ctx = [ 'sessionId' => $sessionId, 'studentName' => $studentName, 'classId' => $classId, 'simId' => 'klima', 'simName' => $simName, 'simIcon' => $simIcon, 'level' => $level, 'levelConfig' => $levelConfigs[$level], 'skipResume' => $skipResume, 'baseUrl' => BASE_URL, 'basePath' => BASE_PATH, 'apiUrl' => BASE_URL . '/php/api', ]; $html = file_get_contents(__DIR__ . '/../sims/klima/game-2d.html'); if ($html === false) { http_response_code(500); echo 'game-2d.html nicht gefunden'; exit; } // Cache-Bust für engine.js anhand Datei-mtime — Browser cached sonst aggressiv // und Spieler:innen sehen Engine-Änderungen erst nach Hard-Reload. $engineFile = __DIR__ . '/../sims/klima/engine.js'; $engineV = is_file($engineFile) ? filemtime($engineFile) : time(); $html = str_replace('', '', $html); $bp = BASE_PATH; $baseTag = ''; $ctxJs = ''; // Plattform-Live-Client: Heartbeat + Pause-Overlay + Spectator-Mode für ?view=teacher $liveJs = ''; // -Tag UND __GGS__-Setup direkt nach einfügen — WICHTIG: // __GGS__ MUSS vor allen anderen Scripts da sein, sonst greift der // Fallback-Init in game-2d.html (~Zeile 1029) zuerst und setzt basePath // auf '/klima-2d' (aus location.pathname) — `const GLOSSAR_PAGE` wird dann // mit dem falschen basePath ausgewertet und kaputte Links erzeugen. $html = preg_replace('//i', '' . "\n" . $baseTag . "\n" . $ctxJs, $html, 1); // Live-Client (deferred) vor $html = str_replace('', $liveJs . "\n", $html); $html = str_replace('href="/schueler"', 'href="' . cockpit_href() . '"', $html); echo $html;