/* ────────────────────────────────────────────────────────────────
   Tutorials T09–T11 — Advanced Game Tutorials
   
   T09: Multiplayer online (WebRTC, lobby, sync, prediction)
   T10: Proceduralny świat (noise, terrain, loot tables)
   T11: Zrób platformera (complete game tutorial)
──────────────────────────────────────────────────────────────── */
/* ══════════════════════════════════════════════════════════════
   T09 — Multiplayer Online
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_MULTIPLAYER_ONLINE = {
    id: 'multiplayer-online-webrtc',
    title: {
        en: 'Online Multiplayer: WebRTC & Lobby',
        pl: 'Multiplayer online: WebRTC i Lobby',
        de: 'Online-Mehrspieler: WebRTC & Lobby',
    },
    description: {
        en: 'Build a networked multiplayer game using WebRTC peer connections, lobby rooms, state synchronization, and client-side prediction.',
        pl: 'Zbuduj grę multiplayer online z użyciem połączeń WebRTC, pokoi lobby, synchronizacji stanów i predykcji po stronie klienta.',
        de: 'Baue ein vernetztes Multiplayer-Spiel mit WebRTC-Peer-Verbindungen, Lobby-Räumen, Zustandssynchronisation und clientseitiger Vorhersage.',
    },
    category: 'multiplayer',
    difficulty: 'advanced',
    estimatedMinutes: 35,
    prerequisites: ['local-multiplayer-coop'],
    kidFriendly: false,
    ageRange: { min: 14, max: 99 },
    tags: ['webrtc', 'networking', 'lobby', 'sync', 'prediction', 'multiplayer'],
    steps: [
        // S1: Intro
        {
            id: 'mp-online-intro',
            type: 'text',
            title: {
                en: 'Online Multiplayer Architecture 🌐',
                pl: 'Architektura multiplayera online 🌐',
                de: 'Online-Mehrspieler-Architektur 🌐',
            },
            content: {
                en: 'In this tutorial you will learn:\n• WebRTC peer-to-peer connections\n• Signaling via WebSocket\n• Creating and joining lobby rooms\n• State synchronization (snapshots)\n• Client-side prediction and reconciliation\n\nYour game will run in real-time across the internet!',
                pl: 'W tym tutorialu nauczysz się:\n• Połączenia peer-to-peer przez WebRTC\n• Sygnalizacja przez WebSocket\n• Tworzenie i dołączanie do pokoi lobby\n• Synchronizacja stanów (snapshoty)\n• Predykcja po stronie klienta i reconciliation\n\nTwoja gra będzie działać w czasie rzeczywistym przez internet!',
                de: 'In diesem Tutorial lernst du:\n• WebRTC Peer-to-Peer-Verbindungen\n• Signaling über WebSocket\n• Lobby-Räume erstellen und beitreten\n• Zustandssynchronisation (Snapshots)\n• Clientseitige Vorhersage und Abgleich\n\nDein Spiel läuft in Echtzeit übers Internet!',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // S2: Create signaling transport
        {
            id: 'signaling-setup',
            type: 'code',
            title: {
                en: 'Set Up Signaling Transport',
                pl: 'Skonfiguruj transport sygnalizacji',
                de: 'Signaling-Transport einrichten',
            },
            content: {
                en: 'WebRTC needs a signaling server to exchange connection offers. We use `createWebSocketSignaling()` to connect to a WebSocket signaling server.',
                pl: 'WebRTC potrzebuje serwera sygnalizacji do wymiany ofert połączenia. Używamy `createWebSocketSignaling()` do połączenia z serwerem sygnalizacji WebSocket.',
                de: 'WebRTC benötigt einen Signaling-Server zum Austausch von Verbindungsangeboten. Wir verwenden `createWebSocketSignaling()` für die WebSocket-Verbindung.',
            },
            codeTemplate: '// Create signaling for WebRTC\nconst signaling = ',
            codeSolution: "const signaling = createWebSocketSignaling();\nawait signaling.connect('wss://your-signal-server.example.com');",
            codeValidation: {
                containsAll: ['createWebSocketSignaling', 'signaling.connect'],
            },
            hint: {
                en: 'Call createWebSocketSignaling() and then connect() to the server URL.',
                pl: 'Wywołaj createWebSocketSignaling() a potem connect() z URL serwera.',
                de: 'Rufe createWebSocketSignaling() auf und dann connect() mit der Server-URL.',
            },
        },
        // S3: PeerManager
        {
            id: 'peer-manager',
            type: 'code',
            title: {
                en: 'Create a Peer Manager',
                pl: 'Utwórz Peer Manager',
                de: 'Peer-Manager erstellen',
            },
            content: {
                en: 'The `PeerManager` handles WebRTC connections. Pass a unique local peer ID, the signaling transport, and optional ICE server config.',
                pl: '`PeerManager` zarządza połączeniami WebRTC. Przekaż unikalne lokalne ID peera, transport sygnalizacji i opcjonalną konfigurację serwerów ICE.',
                de: 'Der `PeerManager` verwaltet WebRTC-Verbindungen. Übergib eine eindeutige lokale Peer-ID, den Signaling-Transport und optionale ICE-Server-Konfiguration.',
            },
            codeTemplate: "// Create PeerManager\nconst myPeerId = 'player_' + Math.random().toString(36).slice(2, 8);\nconst peers = ",
            codeSolution: "const myPeerId = 'player_' + Math.random().toString(36).slice(2, 8);\nconst peers = new PeerManager(myPeerId, signaling, {\n  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],\n});",
            codeValidation: {
                containsAll: ['PeerManager', 'myPeerId', 'signaling'],
            },
            hint: {
                en: 'new PeerManager(localId, signaling, { iceServers: [...] })',
                pl: 'new PeerManager(localId, signaling, { iceServers: [...] })',
                de: 'new PeerManager(localId, signaling, { iceServers: [...] })',
            },
        },
        // S4: Lobby System
        {
            id: 'lobby-create-join',
            type: 'code',
            title: {
                en: 'Create & Join a Lobby Room',
                pl: 'Utwórz i dołącz do pokoju lobby',
                de: 'Lobby-Raum erstellen & beitreten',
            },
            content: {
                en: 'Use `LobbyManager` to create or join rooms. The host creates the room, and clients join it.\nPlayers can set their ready status and team.',
                pl: 'Użyj `LobbyManager` do tworzenia lub dołączania do pokoi. Host tworzy pokój, a klienci dołączają.\nGracze mogą ustawiać gotowość i drużynę.',
                de: 'Verwende den `LobbyManager` zum Erstellen oder Beitreten von Räumen. Der Host erstellt den Raum, Clients treten bei.\nSpieler können Bereitschaft und Team setzen.',
            },
            codeTemplate: '// Create lobby and a room\nconst lobby = ',
            codeSolution: "const lobby = new LobbyManager(myPeerId);\n\n// Host creates a room\nconst room = lobby.createRoom('My Arena', 4, 'deathmatch');\n\n// Set ready\nlobby.setReady(myPeerId, true);\n\n// Check if all ready\nif (lobby.allReady) {\n  console.log('Starting game!');\n}",
            codeValidation: {
                containsAll: ['LobbyManager', 'createRoom', 'setReady'],
            },
            hint: {
                en: 'new LobbyManager(peerId) → createRoom(name, maxPlayers, gameMode)',
                pl: 'new LobbyManager(peerId) → createRoom(nazwa, maxGraczy, trybGry)',
                de: 'new LobbyManager(peerId) → createRoom(name, maxSpieler, spielModus)',
            },
        },
        // S5: NetworkManager
        {
            id: 'network-manager',
            type: 'code',
            title: {
                en: 'Start the Network Manager',
                pl: 'Uruchom Network Manager',
                de: 'Netzwerk-Manager starten',
            },
            content: {
                en: 'The `NetworkManager` handles RPCs, entity sync, and tick management. Start it as `host` or `client` with the PeerManager.',
                pl: '`NetworkManager` obsługuje RPC, synchronizację encji i zarządzanie tickami. Uruchom go jako `host` lub `client` z PeerManagerem.',
                de: 'Der `NetworkManager` verwaltet RPCs, Entity-Sync und Tick-Management. Starte ihn als `host` oder `client` mit dem PeerManager.',
            },
            codeTemplate: '// Start networking\nconst net = new NetworkManager();\n',
            codeSolution: "const net = new NetworkManager();\nnet.tickRate = 20; // 20 ticks per second\nnet.start('host', peers);\n\n// Register an RPC\nnet.registerRPC('chat', (senderId, args) => {\n  console.log(`[${senderId}]: ${args[0]}`);\n});\n\n// Call RPC (broadcast to all)\nnet.callRPC('chat', ['Hello everyone!'], 'all');",
            codeValidation: {
                containsAll: ['NetworkManager', 'start', 'registerRPC'],
            },
            hint: {
                en: "net.start('host', peerManager) — then registerRPC and callRPC",
                pl: "net.start('host', peerManager) — potem registerRPC i callRPC",
                de: "net.start('host', peerManager) — dann registerRPC und callRPC",
            },
        },
        // S6: State Synchronization
        {
            id: 'state-sync',
            type: 'code',
            title: {
                en: 'Synchronize Game State',
                pl: 'Synchronizuj stan gry',
                de: 'Spielzustand synchronisieren',
            },
            content: {
                en: 'Use `SnapshotBuffer` to store game state snapshots for interpolation. The host sends snapshots, clients interpolate between them for smooth movement.',
                pl: 'Użyj `SnapshotBuffer` do przechowywania snapshotów stanu gry dla interpolacji. Host wysyła snapshoty, klienci interpolują między nimi dla płynnego ruchu.',
                de: 'Verwende `SnapshotBuffer` zum Speichern von Spielzustand-Snapshots für Interpolation. Der Host sendet Snapshots, Clients interpolieren dazwischen.',
            },
            codeTemplate: '// Create snapshot buffer\nconst snapshots = ',
            codeSolution: "const snapshots = new SnapshotBuffer(60);\n\n// Host creates and broadcasts snapshots\nconst snapshot = {\n  tick: net.tick,\n  timestamp: performance.now(),\n  states: new Map(),\n};\nsnapshots.push(snapshot);\n\n// Client interpolates\nconst pair = snapshots.getInterpolationPair(serverTime);\nif (pair) {\n  const [a, b] = pair;\n  const t = (serverTime - a.timestamp) / (b.timestamp - a.timestamp);\n  // interpolateState(stateA, stateB, t)\n}",
            codeValidation: {
                containsAll: ['SnapshotBuffer', 'push', 'getInterpolationPair'],
            },
            hint: {
                en: 'SnapshotBuffer stores snapshots. Use getInterpolationPair(time) for smooth rendering.',
                pl: 'SnapshotBuffer przechowuje snapshoty. Użyj getInterpolationPair(czas) dla płynnego renderowania.',
                de: 'SnapshotBuffer speichert Snapshots. Verwende getInterpolationPair(zeit) für flüssiges Rendern.',
            },
        },
        // S7: Client Prediction
        {
            id: 'client-prediction',
            type: 'code',
            title: {
                en: 'Client-Side Prediction',
                pl: 'Predykcja po stronie klienta',
                de: 'Clientseitige Vorhersage',
            },
            content: {
                en: 'Use `ClientPrediction` to store local inputs and reconcile them when the server confirms. This eliminates input lag for the local player.',
                pl: 'Użyj `ClientPrediction` do przechowywania lokalnych inputów i ich reconciliation po potwierdzeniu przez serwer. Eliminuje to opóźnienie inputu lokalnego gracza.',
                de: 'Verwende `ClientPrediction` zum Speichern lokaler Eingaben und Abgleich nach Server-Bestätigung. Dies eliminiert Input-Lag für den lokalen Spieler.',
            },
            codeTemplate: '// Set up prediction\nconst prediction = ',
            codeSolution: "const prediction = new ClientPrediction();\n\n// Every frame: record local input + simulated result\nprediction.addInput({\n  tick: net.tick,\n  inputActions: { moveRight: true, jump: false },\n  resultPosition: { x: player.x + 5, y: player.y },\n});\n\n// When server confirms a tick:\nprediction.acknowledge(serverTick);\n\n// Replay unconfirmed inputs after correction\nconst pending = prediction.getPendingInputs();",
            codeValidation: {
                containsAll: ['ClientPrediction', 'addInput', 'acknowledge'],
            },
            hint: {
                en: 'addInput each frame, acknowledge when server confirms, getPendingInputs to replay.',
                pl: 'addInput każdą klatkę, acknowledge po potwierdzeniu serwera, getPendingInputs do re-symulacji.',
                de: 'addInput pro Frame, acknowledge bei Server-Bestätigung, getPendingInputs zum Replaying.',
            },
        },
        // S8: Quiz
        {
            id: 'mp-online-quiz',
            type: 'quiz',
            title: {
                en: 'Multiplayer Quiz 🧠',
                pl: 'Quiz Multiplayer 🧠',
                de: 'Multiplayer-Quiz 🧠',
            },
            content: {
                en: 'Why is client-side prediction important in online games?',
                pl: 'Dlaczego predykcja po stronie klienta jest ważna w grach online?',
                de: 'Warum ist clientseitige Vorhersage in Online-Spielen wichtig?',
            },
            quizOptions: [
                { id: 'a', text: { en: 'To reduce server load', pl: 'Aby zmniejszyć obciążenie serwera', de: 'Um die Serverlast zu reduzieren' } },
                { id: 'b', text: { en: 'To eliminate perceived input lag', pl: 'Aby wyeliminować odczuwane opóźnienie inputu', de: 'Um wahrgenommene Eingabeverzögerung zu eliminieren' } },
                { id: 'c', text: { en: 'To improve graphics', pl: 'Aby poprawić grafikę', de: 'Um die Grafik zu verbessern' } },
                { id: 'd', text: { en: 'To save bandwidth', pl: 'Aby oszczędzić transfer', de: 'Um Bandbreite zu sparen' } },
            ],
            quizCorrectId: 'b',
        },
        // S9: Network Spawn
        {
            id: 'network-spawn',
            type: 'code',
            title: {
                en: 'Spawn Entities Over the Network',
                pl: 'Tworzenie encji przez sieć',
                de: 'Entities über das Netzwerk spawnen',
            },
            content: {
                en: 'Only the host can spawn and destroy entities over the network. Use `networkSpawn()` to create entities visible to all clients.',
                pl: 'Tylko host może tworzyć i niszczyć encje przez sieć. Użyj `networkSpawn()` aby tworzyć encje widoczne dla wszystkich klientów.',
                de: 'Nur der Host kann Entities über das Netzwerk spawnen/zerstören. Verwende `networkSpawn()` für alle Clients sichtbare Entities.',
            },
            codeTemplate: '// Spawn a networked entity\n',
            codeSolution: "// Host spawns a player entity at position (100, 200)\nconst entityId = world.createEntity();\nworld.addComponent(entityId, 'Transform', { x: 100, y: 200 });\n\nnet.networkSpawn(entityId, 'player-character', { x: 100, y: 200 });\n\n// Listen for remote spawns on clients\nnet.events.on('network:spawn', (data) => {\n  console.log('Remote entity spawned:', data);\n});",
            codeValidation: {
                containsAll: ['networkSpawn', 'network:spawn'],
            },
            hint: {
                en: 'net.networkSpawn(entityId, prefabName, position) — clients listen on network:spawn event',
                pl: 'net.networkSpawn(entityId, prefabName, pozycja) — klienci nasłuchują na event network:spawn',
                de: 'net.networkSpawn(entityId, prefabName, position) — Clients hören auf network:spawn Event',
            },
        },
        // S10: Checkpoint
        {
            id: 'mp-online-complete',
            type: 'checkpoint',
            title: {
                en: 'Multiplayer Online Complete! 🎉',
                pl: 'Multiplayer online gotowy! 🎉',
                de: 'Online-Mehrspieler fertig! 🎉',
            },
            content: {
                en: 'You\'ve learned how to build networked multiplayer games! You can now:\n• Connect peers via WebRTC\n• Create and manage lobby rooms\n• Synchronize game state with snapshots\n• Implement client-side prediction\n• Spawn/destroy entities across the network',
                pl: 'Nauczyłeś się budować gry multiplayer online! Teraz potrafisz:\n• Łączyć peerów przez WebRTC\n• Tworzyć i zarządzać pokojami lobby\n• Synchronizować stan gry snapshotami\n• Implementować predykcję po stronie klienta\n• Tworzyć/niszczyć encje w sieci',
                de: 'Du hast gelernt, vernetzte Multiplayer-Spiele zu bauen! Du kannst jetzt:\n• Peers über WebRTC verbinden\n• Lobby-Räume erstellen und verwalten\n• Spielzustand mit Snapshots synchronisieren\n• Clientseitige Vorhersage implementieren\n• Entities über das Netzwerk spawnen/zerstören',
            },
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   T10 — Proceduralny świat
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_PROCEDURAL_WORLD = {
    id: 'procedural-world-generation',
    title: {
        en: 'Procedural World Generation',
        pl: 'Proceduralny świat',
        de: 'Prozedurale Weltgenerierung',
    },
    description: {
        en: 'Learn to generate infinite, unique game worlds using seeded random, Perlin noise, wave generators, and loot tables.',
        pl: 'Naucz się generować nieskończone, unikalne światy gry z użyciem losowości z seedem, szumu Perlina, generatorów fal wrogów i tabel łupów.',
        de: 'Lerne, unendliche einzigartige Spielwelten mit Zufallsseed, Perlin-Noise, Wellengeneratoren und Loot-Tabellen zu generieren.',
    },
    category: 'game-advanced',
    difficulty: 'intermediate',
    estimatedMinutes: 30,
    prerequisites: ['first-game-bouncing-ball'],
    kidFriendly: true,
    ageRange: { min: 12, max: 99 },
    tags: ['procedural', 'noise', 'perlin', 'terrain', 'loot', 'random', 'generation'],
    steps: [
        // S1: Intro
        {
            id: 'proc-intro',
            type: 'text',
            title: {
                en: 'Procedural Generation 🌍',
                pl: 'Generowanie proceduralne 🌍',
                de: 'Prozedurale Generierung 🌍',
            },
            content: {
                en: 'Procedural generation creates content using algorithms instead of manual design.\n\nYou will learn:\n• Seeded random numbers (reproducible worlds)\n• Perlin noise (natural-looking terrain)\n• Enemy wave scaling\n• Loot tables with weighted drops\n• Procedural pixel art sprites',
                pl: 'Generowanie proceduralne tworzy zawartość za pomocą algorytmów zamiast ręcznego projektowania.\n\nNauczysz się:\n• Losowość z seedem (odtwarzalne światy)\n• Szum Perlina (naturalnie wyglądający teren)\n• Skalowanie fal wrogów\n• Tabele łupów z ważonymi dropami\n• Proceduralne pikselo-artowe sprite\'y',
                de: 'Prozedurale Generierung erstellt Inhalte mit Algorithmen statt manueller Gestaltung.\n\nDu lernst:\n• Zufallszahlen mit Seed (reproduzierbare Welten)\n• Perlin-Noise (natürlich aussehende Landschaft)\n• Feindwellen-Skalierung\n• Loot-Tabellen mit gewichteten Drops\n• Prozedurale Pixel-Art-Sprites',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // S2: SeededRandom
        {
            id: 'seeded-random',
            type: 'code',
            title: {
                en: 'Seeded Random Numbers',
                pl: 'Losowość z seedem',
                de: 'Zufallszahlen mit Seed',
            },
            content: {
                en: 'A `SeededRandom` generates deterministic sequences. The same seed always produces the same world — perfect for sharing maps!',
                pl: '`SeededRandom` generuje deterministyczne sekwencje. Ten sam seed zawsze daje ten sam świat — idealne do dzielenia się mapami!',
                de: 'Ein `SeededRandom` erzeugt deterministische Sequenzen. Der gleiche Seed ergibt immer die gleiche Welt — perfekt zum Teilen!',
            },
            codeTemplate: '// Create a seeded random generator\nconst rng = ',
            codeSolution: "const rng = new SeededRandom(42);\n\n// Reproducible random values\nconsole.log(rng.next());           // always 0-1\nconsole.log(rng.int(1, 100));      // integer 1-100\nconsole.log(rng.float(-5, 5));     // float -5 to 5\nconsole.log(rng.chance(0.3));      // 30% true\nconsole.log(rng.pick(['a','b','c'])); // random element",
            codeValidation: {
                containsAll: ['SeededRandom', 'rng'],
            },
            hint: {
                en: 'new SeededRandom(seed) — use .next(), .int(), .float(), .chance(), .pick()',
                pl: 'new SeededRandom(seed) — użyj .next(), .int(), .float(), .chance(), .pick()',
                de: 'new SeededRandom(seed) — verwende .next(), .int(), .float(), .chance(), .pick()',
            },
        },
        // S3: Perlin Noise terrain
        {
            id: 'perlin-terrain',
            type: 'code',
            title: {
                en: 'Generate Terrain with Perlin Noise',
                pl: 'Generuj teren szumem Perlina',
                de: 'Terrain mit Perlin-Noise generieren',
            },
            content: {
                en: '`PerlinNoise` creates smooth, natural-looking values. Use `noise2D(x, y)` for height maps, and `fbm()` (Fractal Brownian Motion) for detailed landscapes.',
                pl: '`PerlinNoise` tworzy gładkie, naturalnie wyglądające wartości. Użyj `noise2D(x, y)` do map wysokości i `fbm()` (Fractal Brownian Motion) do szczegółowych krajobrazów.',
                de: '`PerlinNoise` erzeugt glatte, natürlich aussehende Werte. Verwende `noise2D(x, y)` für Heightmaps und `fbm()` für detaillierte Landschaften.',
            },
            codeTemplate: '// Generate a height map\nconst noise = ',
            codeSolution: "const noise = new PerlinNoise(42);\n\n// Generate a 64x64 terrain\nfor (let y = 0; y < 64; y++) {\n  for (let x = 0; x < 64; x++) {\n    // noise2D returns -1 to 1 — scale to 0-1\n    const height = (noise.noise2D(x * 0.05, y * 0.05) + 1) * 0.5;\n\n    // Use fbm for more detail (4 octaves)\n    const detail = noise.fbm(x * 0.1, y * 0.1, 4);\n\n    // Map to tile type\n    if (height < 0.3) { /* water */ }\n    else if (height < 0.6) { /* grass */ }\n    else { /* mountain */ }\n  }\n}",
            codeValidation: {
                containsAll: ['PerlinNoise', 'noise2D'],
            },
            hint: {
                en: 'new PerlinNoise(seed) — noise2D(x * scale, y * scale) for smooth gradients',
                pl: 'new PerlinNoise(seed) — noise2D(x * skala, y * skala) dla gładkich gradientów',
                de: 'new PerlinNoise(seed) — noise2D(x * scale, y * scale) für weiche Gradienten',
            },
        },
        // S4: Wave Generator
        {
            id: 'wave-generator',
            type: 'code',
            title: {
                en: 'Enemy Wave Generator',
                pl: 'Generator fal wrogów',
                de: 'Feindwellen-Generator',
            },
            content: {
                en: 'The `WaveGenerator` creates escalating enemy waves automatically. Configure base counts, growth rates, and enemy type probabilities.',
                pl: '`WaveGenerator` tworzy automatycznie eskalujące fale wrogów. Skonfiguruj bazową liczbę, tempo wzrostu i prawdopodobieństwa typów wrogów.',
                de: 'Der `WaveGenerator` erstellt automatisch eskalierende Feindwellen. Konfiguriere Basiszahlen, Wachstumsraten und Feindtyp-Wahrscheinlichkeiten.',
            },
            codeTemplate: '// Configure enemy waves\nconst waveGen = ',
            codeSolution: "const waveGen = new WaveGenerator({\n  baseEnemyCount: 5,\n  countGrowth: 1.3,\n  typeProbabilities: [\n    { type: 'zombie', weight: 3, minWave: 1 },\n    { type: 'skeleton', weight: 2, minWave: 3 },\n    { type: 'boss', weight: 0.5, minWave: 5 },\n  ],\n  groupSize: [1, 3],\n  groupDelay: [0.5, 2],\n}, 42);\n\n// Generate wave 1, 2, 3...\nconst wave1 = waveGen.generateWave(1);\nconst wave5 = waveGen.generateWave(5);",
            codeValidation: {
                containsAll: ['WaveGenerator', 'generateWave', 'typeProbabilities'],
            },
            hint: {
                en: 'new WaveGenerator(config, seed) then generateWave(waveNumber)',
                pl: 'new WaveGenerator(config, seed) potem generateWave(numerFali)',
                de: 'new WaveGenerator(config, seed) dann generateWave(wellenNummer)',
            },
        },
        // S5: Loot Table
        {
            id: 'loot-tables',
            type: 'code',
            title: {
                en: 'Loot Tables & Weighted Drops',
                pl: 'Tabele łupów i ważone dropy',
                de: 'Loot-Tabellen & gewichtete Drops',
            },
            content: {
                en: 'A `LootTable` defines item drops with weights, level requirements, and per-roll limits. Roll for drops when enemies die!',
                pl: '`LootTable` definiuje dropy przedmiotów z wagami, wymogami poziomu i limitami na roll. Rzucaj o dropy gdy wrogowie giną!',
                de: 'Eine `LootTable` definiert Item-Drops mit Gewichtung, Level-Anforderungen und Limits pro Roll. Würfle um Drops wenn Feinde sterben!',
            },
            codeTemplate: '// Define a loot table\nconst loot = ',
            codeSolution: "const loot = new LootTable([\n  { item: 'gold-coin', weight: 10, minLevel: 1 },\n  { item: 'health-potion', weight: 5, minLevel: 1, maxPerRoll: 2 },\n  { item: 'rare-sword', weight: 1, minLevel: 5, maxPerRoll: 1 },\n  { item: 'legendary-shield', weight: 0.1, minLevel: 10, maxPerRoll: 1 },\n], 42);\n\n// Roll for 3 drops at player level 7\nconst drops = loot.roll(3, 7);\nconsole.log('Drops:', drops);",
            codeValidation: {
                containsAll: ['LootTable', 'roll', 'weight'],
            },
            hint: {
                en: 'new LootTable(entries, seed) then loot.roll(count, level)',
                pl: 'new LootTable(wpisy, seed) potem loot.roll(ilość, poziom)',
                de: 'new LootTable(einträge, seed) dann loot.roll(anzahl, level)',
            },
        },
        // S6: Procedural Sprites
        {
            id: 'proc-sprites',
            type: 'code',
            title: {
                en: 'Generate Pixel Art Sprites',
                pl: 'Generuj pikselo-artowe sprite\'y',
                de: 'Pixel-Art-Sprites generieren',
            },
            content: {
                en: '`ProceduralSprite` generates symmetrical pixel art from a palette. Each seed produces a unique character!',
                pl: '`ProceduralSprite` generuje symetryczne pixel arty z palety. Każdy seed daje unikatową postać!',
                de: '`ProceduralSprite` generiert symmetrische Pixel-Art aus einer Palette. Jeder Seed ergibt einen einzigartigen Charakter!',
            },
            codeTemplate: '// Generate a procedural sprite\nconst sprite = ',
            codeSolution: "const sprite = new ProceduralSprite(8, 8, [\n  { r: 0.2, g: 0.5, b: 1, a: 1 },  // blue\n  { r: 0.8, g: 0.2, b: 0.3, a: 1 }, // red\n  { r: 0.9, g: 0.9, b: 0.1, a: 1 }, // yellow\n]);\n\n// Generate with seed\nconst pixels = sprite.generate(42);\n// Each seed = unique character sprite!",
            codeValidation: {
                containsAll: ['ProceduralSprite', 'generate'],
            },
            hint: {
                en: 'new ProceduralSprite(width, height, palette) then .generate(seed)',
                pl: 'new ProceduralSprite(szerokość, wysokość, paleta) potem .generate(seed)',
                de: 'new ProceduralSprite(breite, höhe, palette) dann .generate(seed)',
            },
        },
        // S7: Quiz
        {
            id: 'proc-quiz',
            type: 'quiz',
            title: {
                en: 'Procedural Generation Quiz 🎲',
                pl: 'Quiz z generowania proceduralnego 🎲',
                de: 'Prozedurale Generierung Quiz 🎲',
            },
            content: {
                en: 'What makes Perlin noise better than purely random values for terrain?',
                pl: 'Co sprawia, że szum Perlina jest lepszy niż czysto losowe wartości do terenu?',
                de: 'Was macht Perlin-Noise besser als rein zufällige Werte für Terrain?',
            },
            quizOptions: [
                { id: 'a', text: { en: 'It\'s faster to compute', pl: 'Jest szybszy do obliczenia', de: 'Es ist schneller zu berechnen' } },
                { id: 'b', text: { en: 'It produces smooth, continuous gradients', pl: 'Produkuje gładkie, ciągłe gradienty', de: 'Es erzeugt glatte, kontinuierliche Gradienten' } },
                { id: 'c', text: { en: 'It uses less memory', pl: 'Zużywa mniej pamięci', de: 'Es verbraucht weniger Speicher' } },
                { id: 'd', text: { en: 'It\'s more random', pl: 'Jest bardziej losowy', de: 'Es ist zufälliger' } },
            ],
            quizCorrectId: 'b',
        },
        // S8: Checkpoint
        {
            id: 'proc-complete',
            type: 'checkpoint',
            title: {
                en: 'Procedural Generation Complete! 🌟',
                pl: 'Generowanie proceduralne opanowane! 🌟',
                de: 'Prozedurale Generierung fertig! 🌟',
            },
            content: {
                en: 'You can now generate infinite, unique games! Skills learned:\n• Seeded random for reproducible worlds\n• Perlin noise for natural terrain\n• Wave scaling for enemy difficulty\n• Loot tables for balanced drops\n• Procedural pixel art sprites',
                pl: 'Teraz możesz generować nieskończone, unikalne gry! Umiejętności:\n• Losowość z seedem dla odtwarzalnych światów\n• Szum Perlina dla naturalnego terenu\n• Skalowanie fal dla trudności wrogów\n• Tabele łupów dla zbalansowanych dropów\n• Proceduralne pikselo-artowe sprite\'y',
                de: 'Du kannst jetzt unendliche, einzigartige Spiele generieren! Gelernte Fähigkeiten:\n• Seed-Zufall für reproduzierbare Welten\n• Perlin-Noise für natürliches Terrain\n• Wellen-Skalierung für Feind-Schwierigkeit\n• Loot-Tabellen für ausgewogene Drops\n• Prozedurale Pixel-Art-Sprites',
            },
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   T11 — Zrób Platformera (Full Game Tutorial)
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_PLATFORMER = {
    id: 'build-a-platformer',
    title: {
        en: 'Build a Complete Platformer',
        pl: 'Zrób kompletnego platformera',
        de: 'Baue einen kompletten Platformer',
    },
    description: {
        en: 'Build a full platformer game from scratch: movement, jumping, double-jump, wall-slide, coyote time, dash, enemies, and collectibles.',
        pl: 'Zbuduj kompletną grę platformową od zera: ruch, skoki, podwójny skok, wall-slide, coyote time, dash, wrogowie i zbierane przedmioty.',
        de: 'Baue ein komplettes Plattformspiel von Grund auf: Bewegung, Springen, Doppelsprung, Wall-Slide, Coyote-Time, Dash, Feinde und Sammelobjekte.',
    },
    category: 'game-advanced',
    difficulty: 'intermediate',
    estimatedMinutes: 40,
    prerequisites: ['first-game-bouncing-ball', 'input-movement-gamepad'],
    kidFriendly: true,
    ageRange: { min: 10, max: 99 },
    tags: ['platformer', 'game', 'jumping', 'physics', 'complete-game'],
    steps: [
        // S1: Intro
        {
            id: 'plat-intro',
            type: 'text',
            title: {
                en: 'Build a Platformer! 🏃',
                pl: 'Zbuduj platformera! 🏃',
                de: 'Baue einen Platformer! 🏃',
            },
            content: {
                en: 'In this tutorial you\'ll build a complete platformer game!\n\n• Player movement & acceleration\n• Jumping with coyote time\n• Double-jump & wall-slide\n• Dash mechanic\n• Enemy AI (patrol + chase)\n• Coins & collectibles\n• Level with tilemap\n\nLet\'s make Mario-like magic!',
                pl: 'W tym tutorialu zbudujesz kompletną grę platformową!\n\n• Ruch gracza i przyspieszenie\n• Skakanie z coyote time\n• Podwójny skok i wall-slide\n• Mechanika dashu\n• AI wrogów (patrol + pościg)\n• Monety i zbieranie\n• Poziom z tilemapy\n\nZróbmy magię jak Mario!',
                de: 'In diesem Tutorial baust du ein komplettes Plattformspiel!\n\n• Spielerbewegung & Beschleunigung\n• Springen mit Coyote-Time\n• Doppelsprung & Wall-Slide\n• Dash-Mechanik\n• Feind-KI (Patrouille + Verfolgung)\n• Münzen & Sammelobjekte\n• Level mit Tilemap\n\nLass uns Mario-Magie machen!',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // S2: Player Entity
        {
            id: 'plat-player-entity',
            type: 'code',
            title: {
                en: 'Create the Player Entity',
                pl: 'Utwórz encję gracza',
                de: 'Spieler-Entity erstellen',
            },
            content: {
                en: 'Set up the player entity with Transform, Sprite, Collider2D, and RigidBody2D components.',
                pl: 'Skonfiguruj encję gracza z komponentami Transform, Sprite, Collider2D i RigidBody2D.',
                de: 'Erstelle die Spieler-Entity mit Transform, Sprite, Collider2D und RigidBody2D Komponenten.',
            },
            codeTemplate: "// Create the player\nconst player = world.createEntity();\n",
            codeSolution: "const player = world.createEntity();\nworld.addComponent(player, 'Transform', { x: 100, y: 300, scaleX: 1, scaleY: 1, rotation: 0 });\nworld.addComponent(player, 'Sprite', { textureId: 'player', width: 32, height: 32 });\nworld.addComponent(player, 'Collider2D', { type: 'aabb', width: 28, height: 30, offsetX: 2, offsetY: 2, isTrigger: false });\nworld.addComponent(player, 'RigidBody2D', {\n  velocityX: 0, velocityY: 0,\n  mass: 1, gravityScale: 1,\n  isKinematic: false, restitution: 0,\n});",
            codeValidation: {
                containsAll: ['createEntity', 'Transform', 'Collider2D', 'RigidBody2D'],
            },
            hint: {
                en: 'Add Transform, Sprite, Collider2D, and RigidBody2D to the entity',
                pl: 'Dodaj Transform, Sprite, Collider2D i RigidBody2D do encji',
                de: 'Füge Transform, Sprite, Collider2D und RigidBody2D zur Entity hinzu',
            },
        },
        // S3: Movement with acceleration
        {
            id: 'plat-movement',
            type: 'code',
            title: {
                en: 'Smooth Movement with Acceleration',
                pl: 'Płynny ruch z przyspieszeniem',
                de: 'Flüssige Bewegung mit Beschleunigung',
            },
            content: {
                en: 'Platformer movement uses acceleration and deceleration for a smooth feel. Check `moveRight` and `moveLeft` actions and apply velocity.',
                pl: 'Ruch w platformerze używa przyspieszenia i hamowania dla płynnego wrażenia. Sprawdź akcje `moveRight` i `moveLeft` i przyłóż prędkość.',
                de: 'Platformer-Bewegung nutzt Beschleunigung und Verzögerung für ein flüssiges Gefühl. Prüfe `moveRight` und `moveLeft` Aktionen und wende Geschwindigkeit an.',
            },
            codeTemplate: "// Movement system\nconst MOVE_SPEED = 200;\nconst ACCEL = 800;\nconst FRICTION = 600;\n",
            codeSolution: "const MOVE_SPEED = 200;\nconst ACCEL = 800;\nconst FRICTION = 600;\n\nfunction updateMovement(input: InputManager, rb: any, dt: number) {\n  let moveDir = 0;\n  if (input.getAction('moveRight')) moveDir += 1;\n  if (input.getAction('moveLeft')) moveDir -= 1;\n\n  if (moveDir !== 0) {\n    // Accelerate toward target speed\n    rb.velocityX += moveDir * ACCEL * dt;\n    // Clamp to max speed\n    if (Math.abs(rb.velocityX) > MOVE_SPEED) {\n      rb.velocityX = Math.sign(rb.velocityX) * MOVE_SPEED;\n    }\n  } else {\n    // Apply friction\n    if (Math.abs(rb.velocityX) > FRICTION * dt) {\n      rb.velocityX -= Math.sign(rb.velocityX) * FRICTION * dt;\n    } else {\n      rb.velocityX = 0;\n    }\n  }\n}",
            codeValidation: {
                containsAll: ['ACCEL', 'FRICTION', 'velocityX', 'getAction'],
            },
            hint: {
                en: 'Accelerate when input is active, apply friction when not moving',
                pl: 'Przyspieszaj gdy input aktywny, stosuj tarcie gdy nie ma ruchu',
                de: 'Beschleunigen bei Eingabe, Reibung anwenden wenn keine Bewegung',
            },
        },
        // S4: Jumping with Coyote Time
        {
            id: 'plat-jumping',
            type: 'code',
            title: {
                en: 'Jumping with Coyote Time',
                pl: 'Skakanie z coyote time',
                de: 'Springen mit Coyote-Time',
            },
            content: {
                en: 'Coyote time lets the player jump shortly after leaving a platform — making the game feel more forgiving. Track the last grounded time.',
                pl: 'Coyote time pozwala graczowi skoczyć chwilę po opuszczeniu platformy — co sprawia, że gra jest bardziej wybaczalna. Śledź czas ostatniego kontaktu z podłożem.',
                de: 'Coyote-Time erlaubt dem Spieler kurz nach Verlassen einer Plattform zu springen — das Spiel fühlt sich nachsichtiger an. Verfolge die letzte Bodenberührungs-Zeit.',
            },
            codeTemplate: "// Jump system with coyote time\nconst JUMP_FORCE = -350;\nconst COYOTE_TIME = 0.1; // 100ms grace period\n",
            codeSolution: "const JUMP_FORCE = -350;\nconst COYOTE_TIME = 0.1; // 100ms grace period\nlet lastGroundedTime = 0;\nlet hasJumped = false;\n\nfunction updateJump(input: InputManager, rb: any, isGrounded: boolean, time: number) {\n  if (isGrounded) {\n    lastGroundedTime = time;\n    hasJumped = false;\n  }\n\n  const canJump = !hasJumped && (time - lastGroundedTime) < COYOTE_TIME;\n\n  if (input.getAction('jump') && canJump) {\n    rb.velocityY = JUMP_FORCE;\n    hasJumped = true;\n  }\n\n  // Variable jump height — release early = lower jump\n  if (!input.getAction('jump') && rb.velocityY < 0) {\n    rb.velocityY *= 0.5;\n  }\n}",
            codeValidation: {
                containsAll: ['COYOTE_TIME', 'JUMP_FORCE', 'lastGroundedTime', 'canJump'],
            },
            hint: {
                en: 'Track lastGroundedTime and allow jump within COYOTE_TIME window',
                pl: 'Śledź lastGroundedTime i pozwól na skok w oknie COYOTE_TIME',
                de: 'Verfolge lastGroundedTime und erlaube Sprung im COYOTE_TIME Fenster',
            },
        },
        // S5: Double Jump & Wall Slide
        {
            id: 'plat-double-jump',
            type: 'code',
            title: {
                en: 'Double Jump & Wall Slide',
                pl: 'Podwójny skok i wall-slide',
                de: 'Doppelsprung & Wall-Slide',
            },
            content: {
                en: 'Add an extra jump in the air (double jump) and let the player slide down walls slowly when touching them.',
                pl: 'Dodaj dodatkowy skok w powietrzu (podwójny skok) i pozwól graczowi powoli zsuwać się po ścianach gdy ich dotyka.',
                de: 'Füge einen zusätzlichen Sprung in der Luft (Doppelsprung) hinzu und lass den Spieler langsam an Wänden herunterrutschen.',
            },
            codeTemplate: "// Double jump + wall slide\nconst MAX_AIR_JUMPS = 1;\nconst WALL_SLIDE_SPEED = 50;\n",
            codeSolution: "const MAX_AIR_JUMPS = 1;\nconst WALL_SLIDE_SPEED = 50;\nlet airJumpsLeft = MAX_AIR_JUMPS;\n\nfunction updateAirMoves(input: InputManager, rb: any, isGrounded: boolean, isTouchingWall: boolean) {\n  if (isGrounded) {\n    airJumpsLeft = MAX_AIR_JUMPS;\n  }\n\n  // Double jump (only when not grounded)\n  if (input.getAction('jump') && !isGrounded && airJumpsLeft > 0) {\n    rb.velocityY = JUMP_FORCE;\n    airJumpsLeft--;\n  }\n\n  // Wall slide — slow down fall when touching wall\n  if (isTouchingWall && !isGrounded && rb.velocityY > 0) {\n    rb.velocityY = Math.min(rb.velocityY, WALL_SLIDE_SPEED);\n  }\n}",
            codeValidation: {
                containsAll: ['MAX_AIR_JUMPS', 'WALL_SLIDE_SPEED', 'airJumpsLeft'],
            },
            hint: {
                en: 'Track airJumpsLeft, reset on ground. Clamp velocityY on wall contact.',
                pl: 'Śledź airJumpsLeft, resetuj na ziemi. Ogranicz velocityY przy kontakcie ze ścianą.',
                de: 'Verfolge airJumpsLeft, setze auf dem Boden zurück. Begrenze velocityY bei Wandkontakt.',
            },
        },
        // S6: Dash Mechanic
        {
            id: 'plat-dash',
            type: 'code',
            title: {
                en: 'Dash Mechanic',
                pl: 'Mechanika dashu',
                de: 'Dash-Mechanik',
            },
            content: {
                en: 'A dash gives the player a burst of speed in the current direction. Add a cooldown to prevent spam.',
                pl: 'Dash daje graczowi nagły przypływ prędkości w aktualnym kierunku. Dodaj cooldown, żeby nie spamować.',
                de: 'Ein Dash gibt dem Spieler einen Geschwindigkeitsschub in die aktuelle Richtung. Füge einen Cooldown hinzu.',
            },
            codeTemplate: "// Dash system\nconst DASH_SPEED = 500;\nconst DASH_DURATION = 0.15;\nconst DASH_COOLDOWN = 0.5;\n",
            codeSolution: "const DASH_SPEED = 500;\nconst DASH_DURATION = 0.15;\nconst DASH_COOLDOWN = 0.5;\nlet dashTimer = 0;\nlet dashCooldown = 0;\nlet dashDir = 1;\n\nfunction updateDash(input: InputManager, rb: any, dt: number, facingRight: boolean) {\n  dashCooldown = Math.max(0, dashCooldown - dt);\n\n  if (input.getAction('dash') && dashCooldown <= 0 && dashTimer <= 0) {\n    dashTimer = DASH_DURATION;\n    dashCooldown = DASH_COOLDOWN;\n    dashDir = facingRight ? 1 : -1;\n  }\n\n  if (dashTimer > 0) {\n    dashTimer -= dt;\n    rb.velocityX = DASH_SPEED * dashDir;\n    rb.velocityY = 0; // freeze vertical during dash\n  }\n}",
            codeValidation: {
                containsAll: ['DASH_SPEED', 'DASH_COOLDOWN', 'dashTimer'],
            },
            hint: {
                en: 'Timer-based: set dashTimer on press, apply speed while timer > 0',
                pl: 'Na timerze: ustaw dashTimer po naciśnięciu, stosuj prędkość gdy timer > 0',
                de: 'Timer-basiert: setze dashTimer beim Drücken, wende Speed an solange Timer > 0',
            },
        },
        // S7: Enemies
        {
            id: 'plat-enemies',
            type: 'code',
            title: {
                en: 'Enemy AI: Patrol & Chase',
                pl: 'AI wrogów: patrol i pościg',
                de: 'Feind-KI: Patrouille & Verfolgung',
            },
            content: {
                en: 'Create enemies that patrol between two points. When the player gets close, they switch to chase mode!',
                pl: 'Utwórz wrogów patrolujących między dwoma punktami. Gdy gracz się zbliży, przechodzą w tryb pościgu!',
                de: 'Erstelle Feinde, die zwischen zwei Punkten patrouillieren. Wenn der Spieler nah kommt, wechseln sie in den Verfolgungsmodus!',
            },
            codeTemplate: "// Enemy patrol + chase AI\nconst PATROL_SPEED = 60;\nconst CHASE_SPEED = 120;\nconst DETECT_RANGE = 150;\n",
            codeSolution: "const PATROL_SPEED = 60;\nconst CHASE_SPEED = 120;\nconst DETECT_RANGE = 150;\n\nfunction updateEnemy(enemy: any, playerPos: any, dt: number) {\n  const dx = playerPos.x - enemy.x;\n  const dy = playerPos.y - enemy.y;\n  const dist = Math.sqrt(dx * dx + dy * dy);\n\n  if (dist < DETECT_RANGE) {\n    // Chase mode — move toward player\n    const dir = dx > 0 ? 1 : -1;\n    enemy.velocityX = CHASE_SPEED * dir;\n    enemy.state = 'chase';\n  } else {\n    // Patrol mode — walk between patrolLeft and patrolRight\n    if (enemy.x <= enemy.patrolLeft) enemy.patrolDir = 1;\n    if (enemy.x >= enemy.patrolRight) enemy.patrolDir = -1;\n    enemy.velocityX = PATROL_SPEED * enemy.patrolDir;\n    enemy.state = 'patrol';\n  }\n}",
            codeValidation: {
                containsAll: ['PATROL_SPEED', 'CHASE_SPEED', 'DETECT_RANGE'],
            },
            hint: {
                en: 'Calculate distance to player. Chase if close, patrol if far.',
                pl: 'Oblicz odległość od gracza. Ścigaj jeśli blisko, patroluj jeśli daleko.',
                de: 'Berechne Distanz zum Spieler. Verfolge wenn nah, patrouilliere wenn weit.',
            },
        },
        // S8: Coins & Collectibles
        {
            id: 'plat-collectibles',
            type: 'interactive',
            title: {
                en: 'Add Coins & Collectibles',
                pl: 'Dodaj monety i zbieranie',
                de: 'Münzen & Sammelobjekte hinzufügen',
            },
            content: {
                en: 'Create coin entities with trigger colliders. When the player overlaps a coin, collect it and add to the score!',
                pl: 'Utwórz encje monet z triggerowymi koliderami. Gdy gracz nachodzi na monetę, zbierz ją i dodaj do wyniku!',
                de: 'Erstelle Münzen-Entities mit Trigger-Kollidern. Wenn der Spieler eine Münze überlappt, sammle sie ein und addiere zum Score!',
            },
            requiredAction: 'create-coin-entity',
            codeTemplate: "// Create a coin\nconst coin = world.createEntity();\nworld.addComponent(coin, 'Transform', { x: 200, y: 250 });\n",
            codeSolution: "const coin = world.createEntity();\nworld.addComponent(coin, 'Transform', { x: 200, y: 250, scaleX: 1, scaleY: 1, rotation: 0 });\nworld.addComponent(coin, 'Sprite', { textureId: 'coin', width: 16, height: 16 });\nworld.addComponent(coin, 'Collider2D', { type: 'circle', width: 16, height: 16, isTrigger: true });\n\n// Collision system: collect coins\nevents.on('collision:trigger', (data: { entityA: number; entityB: number }) => {\n  // Check if player hit a coin\n  if (data.entityA === player || data.entityB === player) {\n    const coinId = data.entityA === player ? data.entityB : data.entityA;\n    world.destroyEntity(coinId);\n    score += 10;\n  }\n});",
            codeValidation: {
                containsAll: ['createEntity', 'isTrigger', 'destroyEntity'],
            },
            hint: {
                en: 'Set isTrigger: true on coins so they don\'t push the player',
                pl: 'Ustaw isTrigger: true na monetach, aby nie odpychały gracza',
                de: 'Setze isTrigger: true auf Münzen, damit sie den Spieler nicht wegstoßen',
            },
        },
        // S9: Level with Tilemap
        {
            id: 'plat-tilemap',
            type: 'code',
            title: {
                en: 'Build the Level with Tilemap',
                pl: 'Zbuduj poziom z tilemapy',
                de: 'Level mit Tilemap bauen',
            },
            content: {
                en: 'Use a `Tilemap` to build the level. Define ground, platforms, and walls with tiles.',
                pl: 'Użyj `Tilemap` do zbudowania poziomu. Zdefiniuj podłoże, platformy i ściany z kafelków.',
                de: 'Verwende eine `Tilemap` um das Level zu bauen. Definiere Boden, Plattformen und Wände mit Kacheln.',
            },
            codeTemplate: "// Create the level tilemap\nconst level = new Tilemap(20, 15, 32);\n",
            codeSolution: "const level = new Tilemap(20, 15, 32);\n\n// Add a ground layer\nlevel.addLayer('ground', 20, 15);\n\n// Fill the ground row\nlevel.fillRect('ground', 0, 14, 20, 1, 1); // bottom row = solid\n\n// Add some platforms\nlevel.setTile('ground', 5, 10, 1);  // floating block\nlevel.setTile('ground', 6, 10, 1);\nlevel.setTile('ground', 7, 10, 1);\n\nlevel.fillRect('ground', 12, 8, 4, 1, 1); // higher platform\n\n// Check collisions with tilemap\nconst isSolid = level.isSolid(5, 14); // true — ground!",
            codeValidation: {
                containsAll: ['Tilemap', 'addLayer', 'fillRect'],
            },
            hint: {
                en: 'new Tilemap(cols, rows, tileSize) → addLayer → fillRect/setTile',
                pl: 'new Tilemap(kolumny, wiersze, rozmiarKafelka) → addLayer → fillRect/setTile',
                de: 'new Tilemap(spalten, zeilen, kachelGröße) → addLayer → fillRect/setTile',
            },
        },
        // S10: Checkpoint
        {
            id: 'plat-complete',
            type: 'checkpoint',
            title: {
                en: 'Platformer Complete! 🎮',
                pl: 'Platformer gotowy! 🎮',
                de: 'Platformer fertig! 🎮',
            },
            content: {
                en: 'Congratulations! You\'ve built a complete platformer with:\n• Smooth acceleration-based movement\n• Jumping with coyote time\n• Double jump & wall slide\n• Dash mechanic\n• Enemy AI (patrol & chase)\n• Coins with trigger colliders\n• Tilemap level design\n\nChallenge: Add moving platforms and a boss fight!',
                pl: 'Gratulacje! Zbudowałeś kompletnego platformera z:\n• Płynny ruch z przyspieszeniem\n• Skakanie z coyote time\n• Podwójny skok i wall-slide\n• Mechanika dashu\n• AI wrogów (patrol i pościg)\n• Monety z trigger koliderami\n• Design poziomu z tilemapy\n\nWyzwanie: Dodaj ruchome platformy i walkę z bossem!',
                de: 'Herzlichen Glückwunsch! Du hast einen kompletten Platformer gebaut mit:\n• Flüssige beschleunigungsbasierte Bewegung\n• Springen mit Coyote-Time\n• Doppelsprung & Wall-Slide\n• Dash-Mechanik\n• Feind-KI (Patrouille & Verfolgung)\n• Münzen mit Trigger-Kollidern\n• Tilemap Level-Design\n\nHerausforderung: Füge bewegliche Plattformen und einen Bosskampf hinzu!',
            },
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   Convenience export
   ══════════════════════════════════════════════════════════════ */
function getTutorials_T09_T11() {
    return [
        TUTORIAL_MULTIPLAYER_ONLINE,
        TUTORIAL_PROCEDURAL_WORLD,
        TUTORIAL_PLATFORMER,
    ];
}

export { TUTORIAL_MULTIPLAYER_ONLINE, TUTORIAL_PLATFORMER, TUTORIAL_PROCEDURAL_WORLD, getTutorials_T09_T11 };
//# sourceMappingURL=Tutorials_T09_T11.js.map
