/* ────────────────────────────────────────────────────────────────
   Tutorials T05–T08 — Advanced Game Engine Tutorials
   
   T05: Animacje (Sprite animation, tweens, easing)
   T06: Tilemapy i światy (tilemap, auto-tiling, layers)
   T07: Visual Scripting (node graph, first behavior)
   T08: Multiplayer lokalny (splitscreen, co-op, gamepady)
──────────────────────────────────────────────────────────────── */
/* ══════════════════════════════════════════════════════════════
   T05 — Animacje
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_ANIMATIONS = {
    id: 'animations-sprites-tweens',
    title: {
        en: 'Animations: Sprites, Tweens & Easing',
        pl: 'Animacje: Sprite\'y, Tweeny i Easing',
        de: 'Animationen: Sprites, Tweens & Easing',
    },
    description: {
        en: 'Learn to animate game objects using sprite sheets, tweens, easing functions, and state machines.',
        pl: 'Naucz się animować obiekty za pomocą sprite sheet\'ów, tweenów, easing\'ów i maszyn stanów.',
        de: 'Lerne, Spielobjekte mit Spritesheets, Tweens, Easing und Zustandsmaschinen zu animieren.',
    },
    category: 'game-basics',
    difficulty: 'beginner',
    estimatedMinutes: 20,
    prerequisites: ['first-game-bouncing-ball'],
    kidFriendly: true,
    ageRange: { min: 10, max: 99 },
    tags: ['animation', 'sprite', 'tween', 'easing', 'state-machine'],
    steps: [
        // --- S1: Intro ---
        {
            id: 'anim-welcome',
            type: 'text',
            title: {
                en: 'Welcome to Animations! 🎬',
                pl: 'Witaj w świecie animacji! 🎬',
                de: 'Willkommen bei Animationen! 🎬',
            },
            content: {
                en: 'In this tutorial you will learn:\n• Creating animation clips from sprite sheets\n• Using tweens for smooth movement\n• Easing functions (bounce, elastic, cubic)\n• Animation state machines\n\nLet\'s bring your game to life!',
                pl: 'W tym tutorialu nauczysz się:\n• Tworzenia klipów animacji ze sprite sheet\'ów\n• Używania tweenów do płynnego ruchu\n• Funkcji easing (bounce, elastic, cubic)\n• Maszyn stanów animacji\n\nOżywmy Twoją grę!',
                de: 'In diesem Tutorial lernst du:\n• Animationsclips aus Spritesheets erstellen\n• Tweens für flüssige Bewegungen\n• Easing-Funktionen (Bounce, Elastic, Cubic)\n• Animations-Zustandsmaschinen\n\nLass uns dein Spiel zum Leben erwecken!',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // --- S2: Create a clip from sprite sheet ---
        {
            id: 'create-clip',
            type: 'code',
            title: {
                en: 'Create an Animation Clip',
                pl: 'Utwórz klip animacji',
                de: 'Erstelle einen Animationsclip',
            },
            content: {
                en: 'Use `createClipFromSheet` to define an animation clip from a sprite sheet.\nParameters: name, columns in the sheet, start index, frame count, frame size, and duration per frame.',
                pl: 'Użyj `createClipFromSheet` aby zdefiniować klip animacji ze sprite sheet\'a.\nParametry: nazwa, kolumny w arkuszu, indeks startowy, ilość klatek, rozmiar klatki i czas trwania klatki.',
                de: 'Verwende `createClipFromSheet` um einen Animationsclip aus einem Spritesheet zu erstellen.\nParameter: Name, Spalten im Sheet, Startindex, Framezahl, Framegröße und Dauer pro Frame.',
            },
            codeTemplate: '// Create a "walk" animation clip\nconst walkClip = ',
            codeSolution: 'const walkClip = createClipFromSheet(\n  "walk",    // clip name\n  8,         // sprite sheet has 8 columns\n  0,         // start at frame index 0\n  8,         // 8 frames total\n  64,        // frame width (px)\n  64,        // frame height (px)\n  0.1,       // 0.1s per frame\n  true       // loop\n);',
            codeValidation: {
                containsAll: ['createClipFromSheet', 'walk'],
            },
            hint: {
                en: 'createClipFromSheet(name, columns, startIndex, frameCount, width, height, duration, loop)',
                pl: 'createClipFromSheet(nazwa, kolumny, startIndex, ilośćKlatek, szerokość, wysokość, czas, loop)',
                de: 'createClipFromSheet(name, spalten, startIndex, frameAnzahl, breite, höhe, dauer, loop)',
            },
        },
        // --- S3: SpriteAnimator ---
        {
            id: 'sprite-animator',
            type: 'code',
            title: {
                en: 'Set Up the Sprite Animator',
                pl: 'Skonfiguruj Sprite Animator',
                de: 'Richte den Sprite-Animator ein',
            },
            content: {
                en: 'Create a `SpriteAnimator`, add clips to it, and start playing an animation.\nThe animator tracks which frame to display and handles looping.',
                pl: 'Utwórz `SpriteAnimator`, dodaj do niego klipy i uruchom animację.\nAnimator śledzi, którą klatkę wyświetlić i obsługuje zapętlanie.',
                de: 'Erstelle einen `SpriteAnimator`, füge Clips hinzu und starte die Animation.\nDer Animator verfolgt, welcher Frame angezeigt wird und handhabt das Looping.',
            },
            codeTemplate: '// Set up animator\nconst animator = ',
            codeSolution: 'const animator = new SpriteAnimator();\nanimator.addClip(walkClip);\nanimator.addClip(idleClip);\nanimator.play("walk");',
            codeValidation: {
                containsAll: ['new SpriteAnimator', 'addClip', 'play'],
            },
            hint: {
                en: 'new SpriteAnimator() → addClip(clip) → play("walk")',
                pl: 'new SpriteAnimator() → addClip(klip) → play("walk")',
                de: 'new SpriteAnimator() → addClip(clip) → play("walk")',
            },
        },
        // --- S4: Quiz on animation frames ---
        {
            id: 'quiz-animation',
            type: 'quiz',
            title: {
                en: 'Quick Quiz! 🧠',
                pl: 'Szybki quiz! 🧠',
                de: 'Schnelles Quiz! 🧠',
            },
            content: {
                en: 'If an animation has 8 frames at 0.1s each, how long is one full cycle?',
                pl: 'Jeśli animacja ma 8 klatek po 0.1s każda, ile trwa jeden pełny cykl?',
                de: 'Wenn eine Animation 8 Frames à 0,1s hat, wie lange dauert ein vollständiger Zyklus?',
            },
            quizOptions: [
                { id: 'a', text: { en: '0.1 seconds', pl: '0,1 sekundy', de: '0,1 Sekunden' } },
                { id: 'b', text: { en: '0.8 seconds', pl: '0,8 sekundy', de: '0,8 Sekunden' } },
                { id: 'c', text: { en: '8 seconds', pl: '8 sekund', de: '8 Sekunden' } },
                { id: 'd', text: { en: '80 seconds', pl: '80 sekund', de: '80 Sekunden' } },
            ],
            quizCorrectId: 'b',
        },
        // --- S5: Tweens ---
        {
            id: 'tweens',
            type: 'code',
            title: {
                en: 'Smooth Movement with Tweens',
                pl: 'Płynny ruch z Tweenami',
                de: 'Flüssige Bewegung mit Tweens',
            },
            content: {
                en: 'Use `TweenManager` to smoothly animate properties over time.\nTweens interpolate values from A to B using easing functions.',
                pl: 'Użyj `TweenManager` do płynnego animowania właściwości w czasie.\nTweeny interpolują wartości od A do B za pomocą funkcji easing.',
                de: 'Verwende `TweenManager` um Eigenschaften über die Zeit flüssig zu animieren.\nTweens interpolieren Werte von A nach B mit Easing-Funktionen.',
            },
            codeTemplate: '// Create a tween manager and animate position\nconst tweens = new TweenManager();\n',
            codeSolution: 'const tweens = new TweenManager();\nconst position = { x: 0, y: 300 };\ntweens.create({\n  target: position,\n  to: { x: 700 },\n  duration: 2,\n  easing: Easing.easeOutBounce,\n  onComplete: () => console.log("Arrived!"),\n});',
            codeValidation: {
                containsAll: ['new TweenManager', 'create', 'target', 'to', 'duration'],
            },
            hint: {
                en: 'tweens.create({ target, to: { x: 700 }, duration: 2, easing: Easing.easeOutBounce })',
                pl: 'tweens.create({ target, to: { x: 700 }, duration: 2, easing: Easing.easeOutBounce })',
                de: 'tweens.create({ target, to: { x: 700 }, duration: 2, easing: Easing.easeOutBounce })',
            },
        },
        // --- S6: Easing functions ---
        {
            id: 'easing-functions',
            type: 'text',
            title: {
                en: 'Easing Functions Explained 📈',
                pl: 'Funkcje Easing wyjaśnione 📈',
                de: 'Easing-Funktionen erklärt 📈',
            },
            content: {
                en: 'Available easing functions:\n\n• `Easing.linear` — constant speed\n• `Easing.easeInQuad` — starts slow, accelerates\n• `Easing.easeOutQuad` — starts fast, decelerates\n• `Easing.easeInOutQuad` — slow start & end\n• `Easing.easeInCubic` — stronger acceleration\n• `Easing.easeOutCubic` — stronger deceleration\n• `Easing.easeOutBounce` — bounce effect at the end 🏀\n• `Easing.easeOutElastic` — spring/elastic effect 🔧\n\nExperiment with different easings to find the right feel for your game!',
                pl: 'Dostępne funkcje easing:\n\n• `Easing.linear` — stała prędkość\n• `Easing.easeInQuad` — zaczyna wolno, przyspiesza\n• `Easing.easeOutQuad` — zaczyna szybko, zwalnia\n• `Easing.easeInOutQuad` — wolny start i koniec\n• `Easing.easeInCubic` — silniejsze przyspieszenie\n• `Easing.easeOutCubic` — silniejsze hamowanie\n• `Easing.easeOutBounce` — efekt odbicia 🏀\n• `Easing.easeOutElastic` — efekt sprężyny 🔧\n\nEksperymentuj z różnymi easingami, aby znaleźć odpowiedni feeling!',
                de: 'Verfügbare Easing-Funktionen:\n\n• `Easing.linear` — konstante Geschwindigkeit\n• `Easing.easeInQuad` — langsamer Start, beschleunigt\n• `Easing.easeOutQuad` — schneller Start, bremst ab\n• `Easing.easeInOutQuad` — langsamer Start & Ende\n• `Easing.easeInCubic` — stärkere Beschleunigung\n• `Easing.easeOutCubic` — stärkere Verlangsamung\n• `Easing.easeOutBounce` — Bounce-Effekt 🏀\n• `Easing.easeOutElastic` — Feder/Elastik-Effekt 🔧\n\nExperimentiere mit verschiedenen Easings!',
            },
        },
        // --- S7: Animation State Machine ---
        {
            id: 'state-machine',
            type: 'code',
            title: {
                en: 'Animation State Machine',
                pl: 'Maszyna stanów animacji',
                de: 'Animations-Zustandsmaschine',
            },
            content: {
                en: 'Use `AnimationStateMachine` to manage transitions between animations (idle, walk, jump, attack).\nTransitions fire automatically when conditions are met.',
                pl: 'Użyj `AnimationStateMachine` do zarządzania przejściami między animacjami (idle, walk, jump, attack).\nPrzejścia uruchamiają się automatycznie gdy warunki są spełnione.',
                de: 'Verwende `AnimationStateMachine` um Übergänge zwischen Animationen zu verwalten.\nÜbergänge werden automatisch ausgelöst, wenn Bedingungen erfüllt sind.',
            },
            codeTemplate: '// Create animation state machine\nconst stateMachine = ',
            codeSolution: 'const stateMachine = new AnimationStateMachine(animator);\nstateMachine.addState("idle", "idle");\nstateMachine.addState("walk", "walk");\nstateMachine.addTransition("idle", "walk", () => isMoving);\nstateMachine.addTransition("walk", "idle", () => !isMoving);',
            codeValidation: {
                containsAll: ['new AnimationStateMachine', 'addState', 'addTransition'],
            },
            hint: {
                en: 'new AnimationStateMachine(animator) → addState → addTransition with condition functions',
                pl: 'new AnimationStateMachine(animator) → addState → addTransition z funkcjami warunków',
                de: 'new AnimationStateMachine(animator) → addState → addTransition mit Bedingungsfunktionen',
            },
        },
        // --- S8: Checkpoint ---
        {
            id: 'anim-checkpoint',
            type: 'checkpoint',
            title: {
                en: 'Animation Checkpoint ✅',
                pl: 'Checkpoint animacji ✅',
                de: 'Animations-Checkpoint ✅',
            },
            content: {
                en: 'Great progress! You\'ve learned the core animation APIs. Your progress is saved.',
                pl: 'Świetne postępy! Opanowałeś podstawowe API animacji. Twoje postępy zostały zapisane.',
                de: 'Toller Fortschritt! Du hast die Kern-Animations-APIs gelernt. Dein Fortschritt wurde gespeichert.',
            },
        },
        // --- S9: Congratulations ---
        {
            id: 'anim-congratulations',
            type: 'text',
            title: {
                en: 'Congratulations! 🎉',
                pl: 'Gratulacje! 🎉',
                de: 'Herzlichen Glückwunsch! 🎉',
            },
            content: {
                en: '🏆 You learned:\n• Creating animation clips with `createClipFromSheet`\n• `SpriteAnimator` for frame-by-frame animation\n• `TweenManager` for smooth interpolation\n• Easing functions (bounce, elastic, cubic)\n• `AnimationStateMachine` for automatic transitions\n\nNext: Try the "Tilemaps & Worlds" tutorial! 🗺️',
                pl: '🏆 Nauczyłeś się:\n• Tworzenia klipów animacji z `createClipFromSheet`\n• `SpriteAnimator` do animacji klatka po klatce\n• `TweenManager` do płynnej interpolacji\n• Funkcji easing (bounce, elastic, cubic)\n• `AnimationStateMachine` do automatycznych przejść\n\nDalej: Spróbuj tutoriala "Tilemapy i światy"! 🗺️',
                de: '🏆 Du hast gelernt:\n• Animationsclips mit `createClipFromSheet` erstellen\n• `SpriteAnimator` für Frame-by-Frame-Animation\n• `TweenManager` für flüssige Interpolation\n• Easing-Funktionen (Bounce, Elastic, Cubic)\n• `AnimationStateMachine` für automatische Übergänge\n\nWeiter: Probiere das "Tilemaps & Welten"-Tutorial! 🗺️',
            },
            tooltipPosition: 'center',
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   T06 — Tilemapy i światy
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_TILEMAPS = {
    id: 'tilemaps-and-worlds',
    title: {
        en: 'Tilemaps & Worlds',
        pl: 'Tilemapy i światy',
        de: 'Tilemaps & Welten',
    },
    description: {
        en: 'Build game worlds with tilemaps: create layers, place tiles, use auto-tiling, and handle tile-based collision.',
        pl: 'Buduj światy gier za pomocą tilemap: twórz warstwy, umieszczaj kafelki, używaj auto-tilingu i obsługuj kolizje kafelkowe.',
        de: 'Baue Spielwelten mit Tilemaps: Erstelle Ebenen, platziere Kacheln, nutze Auto-Tiling und behandle kachelbasierte Kollisionen.',
    },
    category: 'game-basics',
    difficulty: 'intermediate',
    estimatedMinutes: 25,
    prerequisites: ['first-game-bouncing-ball'],
    kidFriendly: true,
    ageRange: { min: 10, max: 99 },
    tags: ['tilemap', 'tiles', 'world', 'level', 'auto-tiling', 'layers'],
    steps: [
        // --- S1: Intro ---
        {
            id: 'tile-welcome',
            type: 'text',
            title: {
                en: 'Building Worlds with Tiles! 🗺️',
                pl: 'Budujemy światy z kafelków! 🗺️',
                de: 'Welten mit Kacheln bauen! 🗺️',
            },
            content: {
                en: 'Tilemaps are the foundation of 2D game worlds — platformers, RPGs, strategy games.\n\nYou will learn:\n• Creating a tilemap with layers\n• Placing and filling tiles\n• Auto-tiling (smart borders)\n• Collision detection with tiles\n• Serializing tilemaps (save/load)\n\nLet\'s build a world! 🌍',
                pl: 'Tilemapy to fundament świata gier 2D — platformówek, RPG, strategii.\n\nNauczysz się:\n• Tworzenia tilemapy z warstwami\n• Umieszczania i wypełniania kafelków\n• Auto-tilingu (inteligentne krawędzie)\n• Detekcji kolizji z kafelkami\n• Serializacji tilemap (zapis/odczyt)\n\nZbudujmy świat! 🌍',
                de: 'Tilemaps sind das Fundament von 2D-Spielwelten — Platformer, RPGs, Strategiespiele.\n\nDu lernst:\n• Erstellen einer Tilemap mit Ebenen\n• Kacheln platzieren und füllen\n• Auto-Tiling (intelligente Ränder)\n• Kollisionserkennung mit Kacheln\n• Tilemaps serialisieren (Speichern/Laden)\n\nBauen wir eine Welt! 🌍',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // --- S2: Create tilemap ---
        {
            id: 'create-tilemap',
            type: 'code',
            title: {
                en: 'Create a Tilemap',
                pl: 'Utwórz tilemapę',
                de: 'Erstelle eine Tilemap',
            },
            content: {
                en: 'Create a `Tilemap` with dimensions (width × height in tiles) and tile size.\nThen add a layer for your terrain.',
                pl: 'Utwórz `Tilemap` z wymiarami (szerokość × wysokość w kafelkach) i rozmiarem kafelka.\nNastępnie dodaj warstwę dla terenu.',
                de: 'Erstelle eine `Tilemap` mit Dimensionen (Breite × Höhe in Kacheln) und Kachelgröße.\nDann füge eine Ebene für das Terrain hinzu.',
            },
            codeTemplate: '// Create a 20×15 tilemap with 32px tiles\nconst map = ',
            codeSolution: 'const map = new Tilemap(20, 15, 32, 32);\nconst groundLayer = map.addLayer("ground");\nconst decorLayer = map.addLayer("decoration");',
            codeValidation: {
                containsAll: ['new Tilemap', 'addLayer'],
            },
            hint: {
                en: 'new Tilemap(width, height, tileWidth, tileHeight) → map.addLayer("ground")',
                pl: 'new Tilemap(szerokość, wysokość, szerKafelka, wysKafelka) → map.addLayer("ground")',
                de: 'new Tilemap(breite, höhe, kachelBreite, kachelHöhe) → map.addLayer("ground")',
            },
        },
        // --- S3: Set tiles and fill ---
        {
            id: 'set-tiles',
            type: 'code',
            title: {
                en: 'Place & Fill Tiles',
                pl: 'Umieść i wypełnij kafelki',
                de: 'Kacheln platzieren und füllen',
            },
            content: {
                en: 'Use `setTile` to place individual tiles and `fillRect` to fill rectangular areas.\nTile IDs reference the tileset definition. 0 = empty.',
                pl: 'Użyj `setTile` do umieszczania pojedynczych kafelków i `fillRect` do wypełniania prostokątnych obszarów.\nID kafelków odnosi się do definicji zestawu. 0 = puste.',
                de: 'Verwende `setTile` für einzelne Kacheln und `fillRect` zum Füllen rechteckiger Bereiche.\nKachel-IDs referenzieren die Tileset-Definition. 0 = leer.',
            },
            codeTemplate: '// Place tiles on the ground layer\n',
            codeSolution: '// Fill entire bottom row with grass (tile ID 1)\nmap.fillRect("ground", 0, 14, 20, 1, 1);\n\n// Add some dirt blocks\nmap.fillRect("ground", 5, 12, 3, 2, 2);\n\n// Place individual tiles\nmap.setTile("ground", 10, 11, 3); // bridge tile',
            codeValidation: {
                containsAll: ['fillRect', 'setTile'],
            },
            hint: {
                en: 'fillRect(layerName, x, y, width, height, tileId) — setTile(layerName, x, y, tileId)',
                pl: 'fillRect(warstwa, x, y, szer, wys, idKafelka) — setTile(warstwa, x, y, idKafelka)',
                de: 'fillRect(ebene, x, y, breite, höhe, kachelId) — setTile(ebene, x, y, kachelId)',
            },
        },
        // --- S4: Auto-tiling ---
        {
            id: 'auto-tiling',
            type: 'code',
            title: {
                en: 'Auto-Tiling Magic ✨',
                pl: 'Magia auto-tilingu ✨',
                de: 'Auto-Tiling Magie ✨',
            },
            content: {
                en: 'Use `computeAutoTileMask` to automatically determine which border tile variant to use.\nThe 4-bit bitmask checks top/right/bottom/left neighbors.',
                pl: 'Użyj `computeAutoTileMask` do automatycznego określenia, który wariant kafelka krawędziowego użyć.\n4-bitowa maska sprawdza sąsiadów: góra/prawo/dół/lewo.',
                de: 'Verwende `computeAutoTileMask` um automatisch die richtige Randkachel-Variante zu bestimmen.\nDie 4-Bit-Maske prüft Nachbarn: oben/rechts/unten/links.',
            },
            codeTemplate: '// Compute auto-tile masks for the ground layer\n',
            codeSolution: 'for (let y = 0; y < map.height; y++) {\n  for (let x = 0; x < map.width; x++) {\n    if (map.getTile("ground", x, y) > 0) {\n      const mask = computeAutoTileMask(map, "ground", x, y);\n      // mask is 0-15: use it to pick the right tile variant\n      map.setTile("ground", x, y, tileVariants[mask]);\n    }\n  }\n}',
            codeValidation: {
                containsAll: ['computeAutoTileMask', 'getTile'],
            },
            hint: {
                en: 'computeAutoTileMask(map, layerName, x, y) returns 0-15 bitmask',
                pl: 'computeAutoTileMask(map, warstwa, x, y) zwraca maskę bitową 0-15',
                de: 'computeAutoTileMask(map, ebene, x, y) gibt Bitmaske 0-15 zurück',
            },
        },
        // --- S5: Quiz ---
        {
            id: 'quiz-tilemap',
            type: 'quiz',
            title: {
                en: 'Tilemap Quiz! 🧠',
                pl: 'Quiz tilemap! 🧠',
                de: 'Tilemap-Quiz! 🧠',
            },
            content: {
                en: 'A tilemap is 20 tiles wide × 15 tiles tall with 32px tiles. What is the total pixel width?',
                pl: 'Tilemap ma 20 kafelków szerokości × 15 kafelków wysokości, każdy po 32px. Jaka jest całkowita szerokość w pikselach?',
                de: 'Eine Tilemap ist 20 Kacheln breit × 15 Kacheln hoch mit 32px Kacheln. Wie breit ist sie in Pixeln?',
            },
            quizOptions: [
                { id: 'a', text: { en: '320 px', pl: '320 px', de: '320 px' } },
                { id: 'b', text: { en: '480 px', pl: '480 px', de: '480 px' } },
                { id: 'c', text: { en: '640 px', pl: '640 px', de: '640 px' } },
                { id: 'd', text: { en: '960 px', pl: '960 px', de: '960 px' } },
            ],
            quizCorrectId: 'c',
        },
        // --- S6: Collision queries ---
        {
            id: 'tile-collision',
            type: 'code',
            title: {
                en: 'Tile-Based Collision',
                pl: 'Kolizje oparte o kafelki',
                de: 'Kachelbasierte Kollision',
            },
            content: {
                en: 'Use `isSolid` to check if a tile position is solid, or `getSolidTilesInRect` to query collision in an area.\nThis requires a tileset with solid flags on tiles.',
                pl: 'Użyj `isSolid` do sprawdzenia, czy kafelek jest solidny, lub `getSolidTilesInRect` do zapytania o kolizje w obszarze.\nWymaga to zestawu kafelków z flagami solid.',
                de: 'Verwende `isSolid` um zu prüfen, ob eine Kachel solide ist, oder `getSolidTilesInRect` für Kollisionsabfragen.\nDies erfordert ein Tileset mit Solid-Flags.',
            },
            codeTemplate: '// Check collision with tiles\n',
            codeSolution: '// Convert player world position to tile coordinates\nconst tilePos = map.worldToTile(playerX, playerY);\n\n// Check if the tile below is solid\nif (map.isSolid(tilePos.x, tilePos.y + 1)) {\n  // Player is standing on solid ground\n}\n\n// Query all solid tiles in an area\nconst solidTiles = map.getSolidTilesInRect({\n  x: playerX - 16, y: playerY - 16,\n  width: 32, height: 32\n});',
            codeValidation: {
                containsAll: ['worldToTile', 'isSolid'],
            },
            hint: {
                en: 'worldToTile(x, y) converts pixel coordinates → tile coordinates. isSolid(tileX, tileY) checks collision.',
                pl: 'worldToTile(x, y) konwertuje piksele → współrzędne kafelka. isSolid(tileX, tileY) sprawdza kolizję.',
                de: 'worldToTile(x, y) konvertiert Pixel → Kachelkoordinaten. isSolid(tileX, tileY) prüft Kollision.',
            },
        },
        // --- S7: Serialization ---
        {
            id: 'tile-serialization',
            type: 'code',
            title: {
                en: 'Save & Load Tilemaps',
                pl: 'Zapisz i wczytaj tilemapy',
                de: 'Tilemaps speichern & laden',
            },
            content: {
                en: 'Tilemaps can be serialized to JSON and loaded back. This lets you save levels and load them at runtime.',
                pl: 'Tilemapy można serializować do JSON i wczytywać z powrotem. Dzięki temu możesz zapisywać i ładować poziomy.',
                de: 'Tilemaps können als JSON serialisiert und zurückgeladen werden. So kannst du Level speichern und laden.',
            },
            codeTemplate: '// Save and load a tilemap\n',
            codeSolution: '// Save to JSON\nconst mapData = map.toJSON();\nconst json = JSON.stringify(mapData);\n\n// Load from JSON\nconst loaded = Tilemap.fromJSON(JSON.parse(json));',
            codeValidation: {
                containsAll: ['toJSON', 'fromJSON'],
            },
            hint: {
                en: 'map.toJSON() → JSON string → Tilemap.fromJSON(data)',
                pl: 'map.toJSON() → JSON string → Tilemap.fromJSON(data)',
                de: 'map.toJSON() → JSON String → Tilemap.fromJSON(data)',
            },
        },
        // --- S8: Congratulations ---
        {
            id: 'tile-congratulations',
            type: 'text',
            title: {
                en: 'Congratulations! 🎉',
                pl: 'Gratulacje! 🎉',
                de: 'Herzlichen Glückwunsch! 🎉',
            },
            content: {
                en: '🏆 You learned:\n• `Tilemap` creation with layers\n• `setTile`, `fillRect`, `floodFill`\n• `computeAutoTileMask` for smart borders\n• `worldToTile` / `tileToWorld` conversion\n• `isSolid` / `getSolidTilesInRect` for collision\n• `toJSON` / `fromJSON` for save/load\n\nNext: Try "Visual Scripting" to program without code! 🧩',
                pl: '🏆 Nauczyłeś się:\n• Tworzenia `Tilemap` z warstwami\n• `setTile`, `fillRect`, `floodFill`\n• `computeAutoTileMask` do inteligentnych krawędzi\n• `worldToTile` / `tileToWorld` konwersji\n• `isSolid` / `getSolidTilesInRect` do kolizji\n• `toJSON` / `fromJSON` do zapisu/odczytu\n\nDalej: Spróbuj "Visual Scripting" — programowanie bez kodu! 🧩',
                de: '🏆 Du hast gelernt:\n• `Tilemap`-Erstellung mit Ebenen\n• `setTile`, `fillRect`, `floodFill`\n• `computeAutoTileMask` für intelligente Ränder\n• `worldToTile` / `tileToWorld` Konvertierung\n• `isSolid` / `getSolidTilesInRect` für Kollision\n• `toJSON` / `fromJSON` zum Speichern/Laden\n\nWeiter: Probiere "Visual Scripting" — Programmieren ohne Code! 🧩',
            },
            tooltipPosition: 'center',
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   T07 — Visual Scripting
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_VISUAL_SCRIPTING = {
    id: 'visual-scripting-basics',
    title: {
        en: 'Visual Scripting: Your First Behavior',
        pl: 'Visual Scripting: Twoje pierwsze zachowanie',
        de: 'Visual Scripting: Dein erstes Verhalten',
    },
    description: {
        en: 'Create game logic without writing code using the visual node graph system.',
        pl: 'Twórz logikę gry bez pisania kodu za pomocą wizualnego systemu grafów.',
        de: 'Erstelle Spiellogik ohne Code mit dem visuellen Knoten-Graph-System.',
    },
    category: 'scripting',
    difficulty: 'beginner',
    estimatedMinutes: 20,
    kidFriendly: true,
    ageRange: { min: 8, max: 99 },
    tags: ['visual-scripting', 'nodes', 'no-code', 'graph', 'behaviors'],
    steps: [
        // --- S1: Intro ---
        {
            id: 'vs-welcome',
            type: 'text',
            title: {
                en: 'Program Without Code! 🧩',
                pl: 'Programuj bez kodu! 🧩',
                de: 'Programmieren ohne Code! 🧩',
            },
            content: {
                en: 'Visual Scripting lets you create game behaviors by connecting nodes in a graph.\nNo typing required — just drag, connect, and play!\n\nYou will learn:\n• Creating a node library\n• Building a script graph\n• Connecting nodes with ports\n• Running the graph\n\nPerfect for beginners and kids! 🎮',
                pl: 'Visual Scripting pozwala tworzyć zachowania gry przez łączenie węzłów w grafie.\nBez pisania — po prostu przeciągaj, łącz i graj!\n\nNauczysz się:\n• Tworzenia biblioteki węzłów\n• Budowania grafu skryptowego\n• Łączenia węzłów portami\n• Uruchamiania grafu\n\nIdealne dla początkujących i dzieci! 🎮',
                de: 'Visual Scripting lässt dich Spielverhalten durch Verbinden von Knoten erstellen.\nKein Tippen nötig — einfach ziehen, verbinden und spielen!\n\nDu lernst:\n• Eine Knoten-Bibliothek erstellen\n• Einen Script-Graph bauen\n• Knoten mit Ports verbinden\n• Den Graphen ausführen\n\nPerfekt für Anfänger und Kinder! 🎮',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // --- S2: Create node library ---
        {
            id: 'node-library',
            type: 'code',
            title: {
                en: 'Set Up the Node Library',
                pl: 'Skonfiguruj bibliotekę węzłów',
                de: 'Richte die Knoten-Bibliothek ein',
            },
            content: {
                en: 'The `NodeLibrary` stores all available node types. Use `createDefaultNodeLibrary()` to get built-in nodes for math, logic, entity control, and more.',
                pl: '`NodeLibrary` przechowuje wszystkie dostępne typy węzłów. Użyj `createDefaultNodeLibrary()` aby uzyskać wbudowane węzły do matematyki, logiki, sterowania encjami i więcej.',
                de: 'Die `NodeLibrary` speichert verfügbare Knotentypen. Verwende `createDefaultNodeLibrary()` für eingebaute Knoten für Mathematik, Logik, Entity-Steuerung und mehr.',
            },
            codeTemplate: '// Create the node library with built-in nodes\n',
            codeSolution: 'const library = createDefaultNodeLibrary();\n\n// See available node categories:\n// flow, math, logic, entity, input, physics, audio, ui, game, variable',
            codeValidation: {
                containsAll: ['createDefaultNodeLibrary'],
            },
            hint: {
                en: 'Just call createDefaultNodeLibrary() — it returns a NodeLibrary with all built-in nodes.',
                pl: 'Wystarczy wywołać createDefaultNodeLibrary() — zwraca NodeLibrary z wbudowanymi węzłami.',
                de: 'Rufe einfach createDefaultNodeLibrary() auf — es gibt eine NodeLibrary mit allen eingebauten Knoten zurück.',
            },
        },
        // --- S3: Create a graph ---
        {
            id: 'create-graph',
            type: 'code',
            title: {
                en: 'Build a Script Graph',
                pl: 'Zbuduj graf skryptowy',
                de: 'Baue einen Script-Graphen',
            },
            content: {
                en: 'A `ScriptGraph` has nodes (instances) and connections between their ports.\nLet\'s create a simple graph that moves an entity when a key is pressed.',
                pl: '`ScriptGraph` zawiera węzły (instancje) i połączenia między ich portami.\nUtwórzmy prosty graf, który przesuwa encję po naciśnięciu klawisza.',
                de: 'Ein `ScriptGraph` hat Knoten (Instanzen) und Verbindungen zwischen Ports.\nErstellen wir einen einfachen Graphen, der eine Entity bewegt, wenn eine Taste gedrückt wird.',
            },
            codeTemplate: '// Define a script graph\nconst graph: ScriptGraph = {\n',
            codeSolution: 'const graph: ScriptGraph = {\n  id: "move-on-key",\n  name: "Move On Key Press",\n  nodes: [\n    { id: "n1", type: "on-update", position: { x: 50, y: 100 }, inputValues: {} },\n    { id: "n2", type: "get-key", position: { x: 250, y: 100 }, inputValues: { key: "ArrowRight" } },\n    { id: "n3", type: "move-entity", position: { x: 450, y: 100 }, inputValues: { speed: 200 } },\n  ],\n  connections: [\n    { id: "c1", fromNode: "n1", fromPort: "flow-out", toNode: "n2", toPort: "flow-in" },\n    { id: "c2", fromNode: "n2", fromPort: "flow-out", toNode: "n3", toPort: "flow-in" },\n  ],\n  variables: [],\n};',
            codeValidation: {
                containsAll: ['ScriptGraph', 'nodes', 'connections'],
            },
            hint: {
                en: 'A ScriptGraph needs: id, name, nodes (array), connections (array), and variables (array).',
                pl: 'ScriptGraph potrzebuje: id, name, nodes (tablica), connections (tablica) i variables (tablica).',
                de: 'Ein ScriptGraph braucht: id, name, nodes (Array), connections (Array) und variables (Array).',
            },
        },
        // --- S4: Quiz on nodes ---
        {
            id: 'quiz-nodes',
            type: 'quiz',
            title: {
                en: 'Node Quiz! 🧠',
                pl: 'Quiz o węzłach! 🧠',
                de: 'Knoten-Quiz! 🧠',
            },
            content: {
                en: 'What does a "flow" port type control in visual scripting?',
                pl: 'Co kontroluje port typu "flow" w visual scriptingu?',
                de: 'Was steuert ein "flow"-Port in Visual Scripting?',
            },
            quizOptions: [
                { id: 'a', text: { en: 'The color of the node', pl: 'Kolor węzła', de: 'Die Farbe des Knotens' } },
                { id: 'b', text: { en: 'The execution order (which node runs next)', pl: 'Kolejność wykonania (który węzeł uruchomić dalej)', de: 'Die Ausführungsreihenfolge (welcher Knoten als nächstes läuft)' } },
                { id: 'c', text: { en: 'The animation speed', pl: 'Prędkość animacji', de: 'Die Animationsgeschwindigkeit' } },
                { id: 'd', text: { en: 'The sound volume', pl: 'Głośność dźwięku', de: 'Die Lautstärke' } },
            ],
            quizCorrectId: 'b',
        },
        // --- S5: Execute the graph ---
        {
            id: 'run-graph',
            type: 'code',
            title: {
                en: 'Run the Graph! 🚀',
                pl: 'Uruchom graf! 🚀',
                de: 'Starte den Graphen! 🚀',
            },
            content: {
                en: 'The `GraphExecutor` takes a node library and a script graph, then executes it each frame.\nConnect it to the engine\'s update loop.',
                pl: '`GraphExecutor` przyjmuje bibliotekę węzłów i graf, a następnie wykonuje go co klatkę.\nPodłącz go do pętli update silnika.',
                de: 'Der `GraphExecutor` nimmt eine Knoten-Bibliothek und einen Graphen und führt ihn pro Frame aus.\nVerbinde ihn mit der Update-Schleife der Engine.',
            },
            codeTemplate: '// Execute the graph each frame\nconst executor = ',
            codeSolution: 'const executor = new GraphExecutor(library);\n\n// In the game update loop:\nengine.events.on("engine:update", (dt) => {\n  executor.execute(graph, {\n    entityId: playerId,\n    dt,\n    world: engine.world,\n    events: engine.events,\n    variables: {},\n    triggerFlow: () => {},\n  });\n});',
            codeValidation: {
                containsAll: ['new GraphExecutor', 'execute'],
            },
            hint: {
                en: 'new GraphExecutor(library) → executor.execute(graph, context) in each frame',
                pl: 'new GraphExecutor(library) → executor.execute(graph, context) w każdej klatce',
                de: 'new GraphExecutor(library) → executor.execute(graph, context) in jedem Frame',
            },
        },
        // --- S6: Interactive ---
        {
            id: 'vs-interactive',
            type: 'interactive',
            title: {
                en: 'Try It! Connect Nodes',
                pl: 'Spróbuj! Połącz węzły',
                de: 'Probiere es! Verbinde Knoten',
            },
            content: {
                en: 'In the editor, try dragging a connection from one node\'s output port to another node\'s input port.\nThe wire turns green when compatible ports are connected!',
                pl: 'W edytorze spróbuj przeciągnąć połączenie z portu wyjściowego jednego węzła na port wejściowy drugiego.\nPrzewód zmienia kolor na zielony gdy porty są kompatybilne!',
                de: 'Im Editor: Ziehe eine Verbindung vom Ausgang eines Knotens zum Eingang eines anderen.\nDie Leitung wird grün, wenn kompatible Ports verbunden sind!',
            },
            requiredAction: 'connect-nodes',
        },
        // --- S7: Congratulations ---
        {
            id: 'vs-congratulations',
            type: 'text',
            title: {
                en: 'Congratulations! 🎉',
                pl: 'Gratulacje! 🎉',
                de: 'Herzlichen Glückwunsch! 🎉',
            },
            content: {
                en: '🏆 You learned:\n• `NodeLibrary` with built-in node types\n• Port types: flow, number, string, boolean, vec2, entity\n• `ScriptGraph` structure (nodes + connections)\n• `GraphExecutor` to run graphs each frame\n• Node categories: flow, math, logic, entity, input, physics, game\n\nVisual Scripting makes game development accessible to everyone! 🌟\n\nNext: Try "Local Multiplayer" for co-op gaming! 🎮🎮',
                pl: '🏆 Nauczyłeś się:\n• `NodeLibrary` z wbudowanymi typami węzłów\n• Typy portów: flow, number, string, boolean, vec2, entity\n• Struktury `ScriptGraph` (nodes + connections)\n• `GraphExecutor` do uruchamiania grafów co klatkę\n• Kategorie węzłów: flow, math, logic, entity, input, physics, game\n\nVisual Scripting sprawia, że tworzenie gier jest dostępne dla każdego! 🌟\n\nDalej: Spróbuj "Multiplayer lokalny" do gry w co-op! 🎮🎮',
                de: '🏆 Du hast gelernt:\n• `NodeLibrary` mit eingebauten Knotentypen\n• Port-Typen: flow, number, string, boolean, vec2, entity\n• `ScriptGraph`-Struktur (Knoten + Verbindungen)\n• `GraphExecutor` zum Ausführen von Graphen\n• Knoten-Kategorien: flow, math, logic, entity, input, physics, game\n\nVisual Scripting macht Spieleentwicklung für alle zugänglich! 🌟\n\nWeiter: Probiere "Lokaler Multiplayer" für Co-op! 🎮🎮',
            },
            tooltipPosition: 'center',
        },
    ],
};
/* ══════════════════════════════════════════════════════════════
   T08 — Multiplayer lokalny
   ══════════════════════════════════════════════════════════════ */
const TUTORIAL_LOCAL_MULTIPLAYER = {
    id: 'local-multiplayer-coop',
    title: {
        en: 'Local Multiplayer: Co-op & Splitscreen',
        pl: 'Multiplayer lokalny: Co-op i Splitscreen',
        de: 'Lokaler Multiplayer: Co-op & Splitscreen',
    },
    description: {
        en: 'Set up local multiplayer with gamepads, split-screen viewports, and drop-in/drop-out players.',
        pl: 'Skonfiguruj multiplayer lokalny z gamepadami, podzielonym ekranem i dynamicznym dołączaniem graczy.',
        de: 'Richte lokalen Multiplayer mit Gamepads, Splitscreen und Drop-in/Drop-out-Spielern ein.',
    },
    category: 'multiplayer',
    difficulty: 'intermediate',
    estimatedMinutes: 25,
    prerequisites: ['first-game-bouncing-ball'],
    kidFriendly: true,
    ageRange: { min: 10, max: 99 },
    tags: ['multiplayer', 'local', 'splitscreen', 'gamepad', 'coop', 'co-op'],
    steps: [
        // --- S1: Intro ---
        {
            id: 'mp-welcome',
            type: 'text',
            title: {
                en: 'Play Together! 🎮🎮',
                pl: 'Grajcie razem! 🎮🎮',
                de: 'Zusammen spielen! 🎮🎮',
            },
            content: {
                en: 'Local multiplayer lets friends play together on the same screen!\n\nYou will learn:\n• `PlayerManager` — managing up to 12 players\n• Gamepad input for each player\n• Split-screen viewports\n• Drop-in / drop-out\n\nGrab some controllers and let\'s go! 🕹️',
                pl: 'Multiplayer lokalny pozwala znajomym grać razem na jednym ekranie!\n\nNauczysz się:\n• `PlayerManager` — zarządzanie do 12 graczy\n• Wejście z gamepada dla każdego gracza\n• Podzielony ekran (split-screen)\n• Dynamiczne dołączanie/odłączanie graczy\n\nWeź kontrolery i zaczynamy! 🕹️',
                de: 'Lokaler Multiplayer lässt Freunde zusammen auf einem Bildschirm spielen!\n\nDu lernst:\n• `PlayerManager` — bis zu 12 Spieler verwalten\n• Gamepad-Eingabe für jeden Spieler\n• Splitscreen-Ansichten\n• Drop-in / Drop-out\n\nSchnappt euch Controller und los! 🕹️',
            },
            skippable: true,
            tooltipPosition: 'center',
        },
        // --- S2: PlayerManager ---
        {
            id: 'player-manager',
            type: 'code',
            title: {
                en: 'Set Up Player Manager',
                pl: 'Skonfiguruj PlayerManager',
                de: 'Richte den PlayerManager ein',
            },
            content: {
                en: 'The `PlayerManager` handles player registration, scores, and lives.\nUse `setupLocal(count)` for quick local multiplayer setup.',
                pl: '`PlayerManager` obsługuje rejestrację graczy, punkty i życia.\nUżyj `setupLocal(count)` do szybkiej konfiguracji multiplayer lokalnego.',
                de: 'Der `PlayerManager` verwaltet Spielerregistrierung, Punkte und Leben.\nVerwende `setupLocal(count)` für schnelles lokales Multiplayer-Setup.',
            },
            codeTemplate: '// Set up local multiplayer with the engine\nconst players = engine.players;\n',
            codeSolution: 'const players = engine.players;\n\n// Quick setup for 2 players\nconst allPlayers = players.setupLocal(2);\n\n// Player 1: keyboard, Player 2: gamepad\nconsole.log(allPlayers[0].name);       // "Player 1"\nconsole.log(allPlayers[0].inputDevice); // "keyboard"\nconsole.log(allPlayers[1].name);       // "Player 2"\nconsole.log(allPlayers[1].inputDevice); // "gamepad"',
            codeValidation: {
                containsAll: ['players', 'setupLocal'],
            },
            hint: {
                en: 'engine.players.setupLocal(2) creates 2 players (keyboard + gamepad).',
                pl: 'engine.players.setupLocal(2) tworzy 2 graczy (klawiatura + gamepad).',
                de: 'engine.players.setupLocal(2) erstellt 2 Spieler (Tastatur + Gamepad).',
            },
        },
        // --- S3: Gamepad input per player ---
        {
            id: 'gamepad-input',
            type: 'code',
            title: {
                en: 'Per-Player Input',
                pl: 'Wejście per gracz',
                de: 'Eingabe pro Spieler',
            },
            content: {
                en: 'Use `input.getAction(name, playerIndex)` to read actions per player.\nPlayer 0 uses keyboard, player 1+ use gamepads.',
                pl: 'Użyj `input.getAction(nazwa, indeksGracza)` do odczytu akcji per gracz.\nGracz 0 używa klawiatury, gracz 1+ używa gamepadów.',
                de: 'Verwende `input.getAction(name, spielerIndex)` um Aktionen pro Spieler zu lesen.\nSpieler 0 nutzt die Tastatur, Spieler 1+ nutzen Gamepads.',
            },
            codeTemplate: '// Define actions and read per-player input\nengine.input.defineAction("jump", {\n',
            codeSolution: 'engine.input.defineAction("jump", {\n  keys: ["Space"],\n  gamepadButtons: ["A"],\n});\n\nengine.input.defineAction("moveRight", {\n  keys: ["ArrowRight"],\n  gamepadAxis: { axis: "LeftX", direction: "positive" },\n});\n\n// In update loop:\nfor (const player of players.activePlayers) {\n  const jump = engine.input.getAction("jump", player.index);\n  const move = engine.input.getAction("moveRight", player.index);\n\n  if (jump.justPressed) {\n    // Player jumped!\n  }\n}',
            codeValidation: {
                containsAll: ['defineAction', 'getAction', 'gamepadButtons'],
            },
            hint: {
                en: 'defineAction(name, { keys, gamepadButtons }) → getAction(name, playerIndex)',
                pl: 'defineAction(nazwa, { keys, gamepadButtons }) → getAction(nazwa, indeksGracza)',
                de: 'defineAction(name, { keys, gamepadButtons }) → getAction(name, spielerIndex)',
            },
        },
        // --- S4: Splitscreen ---
        {
            id: 'splitscreen',
            type: 'code',
            title: {
                en: 'Split-Screen Views',
                pl: 'Podzielony ekran',
                de: 'Splitscreen-Ansichten',
            },
            content: {
                en: 'The `SplitScreenManager` divides the canvas into viewports.\nSupported modes: shared, split-horizontal, split-vertical, split-quad, split-6, split-9, split-12.',
                pl: '`SplitScreenManager` dzieli canvas na widoki.\nDostępne tryby: shared, split-horizontal, split-vertical, split-quad, split-6, split-9, split-12.',
                de: 'Der `SplitScreenManager` teilt den Canvas in Viewports auf.\nUnterstützte Modi: shared, split-horizontal, split-vertical, split-quad, split-6, split-9, split-12.',
            },
            codeTemplate: '// Set up split-screen\nconst splitScreen = engine.splitScreen;\n',
            codeSolution: 'const splitScreen = engine.splitScreen;\n\n// Set the mode based on player count\nsplitScreen.setMode("split-vertical"); // left-right split for 2 players\nsplitScreen.setCanvasSize(800, 600);\n\n// The engine automatically renders each viewport\n// in its render loop — no extra code needed!',
            codeValidation: {
                containsAll: ['splitScreen', 'setMode'],
            },
            hint: {
                en: 'engine.splitScreen.setMode("split-vertical") for 2-player side by side.',
                pl: 'engine.splitScreen.setMode("split-vertical") dla 2 graczy obok siebie.',
                de: 'engine.splitScreen.setMode("split-vertical") für 2 Spieler nebeneinander.',
            },
        },
        // --- S5: Quiz ---
        {
            id: 'quiz-multiplayer',
            type: 'quiz',
            title: {
                en: 'Multiplayer Quiz! 🧠',
                pl: 'Quiz multiplayer! 🧠',
                de: 'Multiplayer-Quiz! 🧠',
            },
            content: {
                en: 'How many players does the engine support simultaneously?',
                pl: 'Ilu graczy jednocześnie obsługuje silnik?',
                de: 'Wie viele Spieler unterstützt die Engine gleichzeitig?',
            },
            quizOptions: [
                { id: 'a', text: { en: '2 players', pl: '2 graczy', de: '2 Spieler' } },
                { id: 'b', text: { en: '4 players', pl: '4 graczy', de: '4 Spieler' } },
                { id: 'c', text: { en: '8 players', pl: '8 graczy', de: '8 Spieler' } },
                { id: 'd', text: { en: 'Up to 12 players', pl: 'Do 12 graczy', de: 'Bis zu 12 Spieler' } },
            ],
            quizCorrectId: 'd',
        },
        // --- S6: Drop-in / drop-out ---
        {
            id: 'drop-in-out',
            type: 'code',
            title: {
                en: 'Drop-In / Drop-Out',
                pl: 'Dynamiczne dołączanie/odłączanie',
                de: 'Drop-In / Drop-Out',
            },
            content: {
                en: 'Players can join and leave at any time. Listen for `player:join` and `player:leave` events.',
                pl: 'Gracze mogą dołączać i odchodzić w dowolnym momencie. Nasłuchuj zdarzeń `player:join` i `player:leave`.',
                de: 'Spieler können jederzeit beitreten oder verlassen. Lausche auf `player:join` und `player:leave` Events.',
            },
            codeTemplate: '// Handle players joining and leaving\n',
            codeSolution: '// Listen for new players\nplayers.events.on("player:join", (player) => {\n  // Create a character entity for the new player\n  const entityId = engine.world.createEntity();\n  engine.world.addComponent(entityId, "Transform", {\n    x: 100 + player.index * 150, y: 300\n  });\n  player.entityId = entityId;\n});\n\n// Handle player leaving\nplayers.events.on("player:leave", (player) => {\n  if (player.entityId != null) {\n    engine.world.removeEntity(player.entityId);\n  }\n});\n\n// Manual join with gamepad\nplayers.join({\n  index: 2,\n  name: "Player 3",\n  color: { r: 0, g: 0, b: 1, a: 1 },\n  inputDevice: "gamepad",\n  gamepadIndex: 2,\n});',
            codeValidation: {
                containsAll: ['player:join', 'player:leave', 'createEntity'],
            },
            hint: {
                en: 'players.events.on("player:join", (player) => { ... }) and players.join({ index, name, color, inputDevice })',
                pl: 'players.events.on("player:join", (player) => { ... }) i players.join({ index, name, color, inputDevice })',
                de: 'players.events.on("player:join", (player) => { ... }) und players.join({ index, name, color, inputDevice })',
            },
        },
        // --- S7: Vibration ---
        {
            id: 'gamepad-vibration',
            type: 'code',
            title: {
                en: 'Gamepad Vibration 🎮💥',
                pl: 'Wibracja gamepada 🎮💥',
                de: 'Gamepad-Vibration 🎮💥',
            },
            content: {
                en: 'Make gamepads rumble on events like damage or explosions using the `vibrate` method.',
                pl: 'Spraw, aby gamepady zaczęły wibrować przy zdarzeniach jak obrażenia czy eksplozje, używając metody `vibrate`.',
                de: 'Lass Gamepads bei Events wie Schaden oder Explosionen vibrieren mit der `vibrate`-Methode.',
            },
            codeTemplate: '// Vibrate a player\'s gamepad\n',
            codeSolution: '// Vibrate player 1\'s gamepad for 200ms\nengine.input.vibrate(\n  1,      // player index\n  200,    // duration (ms)\n  0.5,    // weak motor magnitude (0-1)\n  1.0     // strong motor magnitude (0-1)\n);',
            codeValidation: {
                containsAll: ['vibrate'],
            },
            hint: {
                en: 'engine.input.vibrate(playerIndex, duration, weakMagnitude, strongMagnitude)',
                pl: 'engine.input.vibrate(indeksGracza, czas, siłaSłaba, siłaSilna)',
                de: 'engine.input.vibrate(spielerIndex, dauer, schwachStärke, starkStärke)',
            },
        },
        // --- S8: Congratulations ---
        {
            id: 'mp-congratulations',
            type: 'text',
            title: {
                en: 'Congratulations! 🎉',
                pl: 'Gratulacje! 🎉',
                de: 'Herzlichen Glückwunsch! 🎉',
            },
            content: {
                en: '🏆 You learned:\n• `PlayerManager` for up to 12 players\n• `setupLocal(count)` for quick setup\n• Per-player `getAction(name, playerIndex)`\n• Action bindings for keyboard + gamepad\n• `SplitScreenManager` with 7 screen modes\n• Drop-in / drop-out with events\n• Gamepad vibration for tactile feedback\n\nYour games are now multiplayer-ready! 🎮🎮🎮🎮',
                pl: '🏆 Nauczyłeś się:\n• `PlayerManager` do 12 graczy\n• `setupLocal(count)` do szybkiej konfiguracji\n• Per-gracz `getAction(name, playerIndex)`\n• Bindowanie akcji dla klawiatury + gamepada\n• `SplitScreenManager` z 7 trybami ekranu\n• Dynamiczne dołączanie/odłączanie ze zdarzeniami\n• Wibracja gamepada dla taktylnego feedbacku\n\nTwoje gry są teraz gotowe na multiplayer! 🎮🎮🎮🎮',
                de: '🏆 Du hast gelernt:\n• `PlayerManager` für bis zu 12 Spieler\n• `setupLocal(count)` für schnelles Setup\n• Pro-Spieler `getAction(name, spielerIndex)`\n• Aktionsbindungen für Tastatur + Gamepad\n• `SplitScreenManager` mit 7 Bildschirmmodi\n• Drop-in / Drop-out mit Events\n• Gamepad-Vibration für taktiles Feedback\n\nDeine Spiele sind jetzt Multiplayer-bereit! 🎮🎮🎮🎮',
            },
            tooltipPosition: 'center',
        },
    ],
};
/* ── Convenience collection ───────────────────────────────────── */
function getTutorials_T05_T08() {
    return [
        TUTORIAL_ANIMATIONS,
        TUTORIAL_TILEMAPS,
        TUTORIAL_VISUAL_SCRIPTING,
        TUTORIAL_LOCAL_MULTIPLAYER,
    ];
}

export { TUTORIAL_ANIMATIONS, TUTORIAL_LOCAL_MULTIPLAYER, TUTORIAL_TILEMAPS, TUTORIAL_VISUAL_SCRIPTING, getTutorials_T05_T08 };
//# sourceMappingURL=Tutorials_T05_T08.js.map
