{"asset_map": {"fighter_plane": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fighter_plane_540eade5-2cbb-402a-a26b-a199537f2b90.webp", "type": "image", "prompt": "Generate a low-poly 3D render of a military fighter jet from a 45-degree front-left perspective. Sleek angular fuselage in olive gray-green, swept-back wings with small red and white roundel insignias, a single tail fin, and a dark tinted cockpit canopy. Use flat shading and faceted geometric surfaces to emphasize the low-poly aesthetic. High resolution, professional game asset, clean silhouette, transparent background.", "description": "Low-poly military fighter jet for the player-controlled aircraft", "subtype": "character", "aspect_ratio": [1250, 812], "compression_suggestion": "low", "duration_secs": null, "loop": null, "animation": null}, "bomb_projectile": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/bomb_projectile_3fab61e1-2732-4878-90c8-4a5d96717564.webp", "type": "image", "prompt": "Generate a low-poly military bomb viewed from a 45-degree angle. Cylindrical body in dark metallic gray with a pointed nose cone and four small tail stabilizers. Features yellow and black hazard stripes around the midsection. Flat shading, clean geometric shapes, consistent with the fighter jet's low-poly style. Transparent background.", "description": "A military bomb projectile dropped by the plane", "subtype": "object", "aspect_ratio": [910, 759], "compression_suggestion": "high", "duration_secs": null, "loop": null, "animation": null}, "explosion_effect": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/explosion_effect_cc322738-522d-4d6c-b75a-d6daedc0576f.webp", "type": "image", "prompt": "Generate a stylized low-poly explosion sprite. A spherical burst of bright orange and yellow flames at the core with faceted dark gray smoke billowing outward. High contrast, energetic and dynamic appearance with sharp geometric edges. Designed as a billboard sprite for a 3D game. Transparent background.", "description": "A stylized geometric explosion effect for building destruction", "subtype": "effect", "aspect_ratio": [929, 923], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "animation": null}, "bomb_drop_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/bomb_drop_sound_8435535e-85d9-4936-a1ad-a04f9db05915.mp3", "type": "audio", "prompt": "Mechanical bomb release sound effect for a military aircraft. Starts with a sharp metallic 'clunk' or latch release, followed immediately by a brief, high-speed airy whoosh. Clean, punchy, and professional recording, suitable for frequent repetition in an arcade game without fatigue.", "description": "Short mechanical release and whoosh sound when a bomb is dropped from the plane.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 1.58, "loop": false, "animation": null}, "explosion_sound": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/explosion_sound_f436c78e-1fa0-4548-8549-3ae07c7de428.mp3", "type": "audio", "prompt": "Powerful arcade-style explosion. A deep, heavy bass boom combined with a sharp initial impact crack. Includes a short tail of crumbling masonry and debris rumble. Impactful and satisfying high-fidelity sound, 130dB feel but normalized for gameplay, no long ringing tail.", "description": "Satisfying and impactful explosion sound when a building is destroyed.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 2.11, "loop": false, "animation": null}, "engine_loop": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/effect/engine_loop_3526eb5a-e391-41ef-9505-bdefdbaf1ced.mp3", "type": "audio", "prompt": "Steady, mid-range jet engine roar and turbine hum. Consistent and smooth white-noise-like quality with a mechanical whine. No sudden changes in pitch or volume. Designed for a seamless loop in a 3D flight game. Clean, non-distracting, industrial military aviation atmosphere.", "description": "Continuous looping jet engine hum for the player's fighter plane.", "subtype": "effect", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 5.0, "loop": true, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Sky Strike</title>\n    <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n    <link href=\"https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body, html {\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Orbitron', sans-serif;\n        }\n\n\n        #game-container {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(to bottom, #1a1a2e 0%, #16213e 50%, #0f3460 100%);\n        }\n\n        #game-canvas {\n            width: 100%;\n            height: 100%;\n            display: block;\n        }\n\n        /* UI Overlay */\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            z-index: 100;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        /* HUD */\n        .hud {\n            position: absolute;\n            top: var(--space-sm);\n            left: var(--space-sm);\n            right: var(--space-sm);\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n            pointer-events: none;\n        }\n\n        .hud-panel {\n            background: linear-gradient(135deg, rgba(20, 30, 48, 0.9) 0%, rgba(36, 59, 85, 0.85) 100%);\n            border: 2px solid rgba(100, 180, 255, 0.4);\n            border-radius: 12px;\n            padding: 12px 16px;\n            color: #fff;\n            font-size: 28px;\n            font-weight: 700;\n            text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);\n            box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1);\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .hud-panel .hud-icon {\n            font-size: 24px;\n            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));\n        }\n\n        .hud-panel .hud-label {\n            font-size: 14px;\n            font-weight: 400;\n            opacity: 0.7;\n            text-transform: uppercase;\n            letter-spacing: 1px;\n        }\n\n        .hud-panel .hud-value {\n            font-size: 32px;\n            font-weight: 900;\n        }\n\n        .score-display .hud-value {\n            color: #ffd700;\n            text-shadow: 0 0 12px rgba(255, 215, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.8);\n        }\n\n        .bomb-display .hud-value {\n            color: #ff6b6b;\n            text-shadow: 0 0 12px rgba(255, 107, 107, 0.5), 0 2px 8px rgba(0, 0, 0, 0.8);\n        }\n\n        .coin-display .hud-value {\n            color: #ffd700;\n            text-shadow: 0 0 12px rgba(255, 215, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.8);\n        }\n\n        /* Virtual Joystick */\n        .joystick-zone {\n            position: absolute;\n            left: 0;\n            bottom: 0;\n            width: 50%;\n            height: 50%;\n            pointer-events: auto;\n        }\n\n        .joystick-base {\n            position: absolute;\n            width: 120px;\n            height: 120px;\n            background: rgba(255, 255, 255, 0.2);\n            border: 3px solid rgba(255, 255, 255, 0.4);\n            border-radius: 50%;\n            display: none;\n        }\n\n        .joystick-stick {\n            position: absolute;\n            width: 50px;\n            height: 50px;\n            background: rgba(255, 255, 255, 0.6);\n            border-radius: 50%;\n            transform: translate(-50%, -50%);\n            left: 50%;\n            top: 50%;\n        }\n\n        /* Bomb Button */\n        .bomb-button {\n            position: absolute;\n            right: var(--space-lg);\n            bottom: calc(var(--space-lg) + 48px);\n            width: 100px;\n            height: 100px;\n            background: radial-gradient(circle at 30% 30%, #ff5555 0%, #cc0000 50%, #990000 100%);\n            border: 4px solid rgba(255, 255, 255, 0.6);\n            border-radius: 50%;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            color: #fff;\n            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);\n            cursor: pointer;\n            transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out);\n            pointer-events: auto;\n            box-shadow: 0 6px 20px rgba(200, 0, 0, 0.5), 0 0 30px rgba(255, 50, 50, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3);\n        }\n\n        .bomb-button:active {\n            transform: scale(0.92);\n            box-shadow: 0 2px 10px rgba(200, 0, 0, 0.4), 0 0 15px rgba(255, 50, 50, 0.2), inset 0 -2px 4px rgba(0, 0, 0, 0.3);\n        }\n\n        .bomb-button.cooldown {\n            background: radial-gradient(circle at 30% 30%, #666 0%, #444 50%, #333 100%);\n            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3);\n            border-color: rgba(255, 255, 255, 0.3);\n        }\n\n        /* Edit Mode Overlay */\n        .edit-overlay {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            background: rgba(0, 0, 0, 0.8);\n            border: 2px solid rgba(255, 255, 255, 0.3);\n            border-radius: 12px;\n            padding: var(--space-lg);\n            color: #fff;\n            text-align: center;\n            font-size: 24px;\n        }\n\n        .edit-overlay h2 {\n            font-size: 32px;\n            margin-bottom: var(--space-sm);\n            color: #ffd700;\n        }\n\n        /* Crosshair */\n        .crosshair {\n            position: absolute;\n            left: 50%;\n            top: 50%;\n            transform: translate(-50%, -50%);\n            width: 48px;\n            height: 48px;\n            pointer-events: none;\n            filter: drop-shadow(0 0 4px rgba(255, 100, 100, 0.6)) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));\n            z-index: 50;\n        }\n\n        .crosshair::before,\n        .crosshair::after {\n            content: '';\n            position: absolute;\n            background: rgba(255, 255, 255, 0.85);\n        }\n\n        .crosshair::before {\n            width: 2px;\n            height: 100%;\n            left: 50%;\n            transform: translateX(-50%);\n        }\n\n        .crosshair::after {\n            width: 100%;\n            height: 2px;\n            top: 50%;\n            transform: translateY(-50%);\n        }\n\n        .crosshair-circle {\n            position: absolute;\n            width: 24px;\n            height: 24px;\n            border: 2px solid rgba(255, 255, 255, 0.85);\n            border-radius: 50%;\n            left: 50%;\n            top: 50%;\n            transform: translate(-50%, -50%);\n        }\n\n        .crosshair-dot {\n            position: absolute;\n            width: 4px;\n            height: 4px;\n            background: #ff6b6b;\n            border-radius: 50%;\n            left: 50%;\n            top: 50%;\n            transform: translate(-50%, -50%);\n            box-shadow: 0 0 6px rgba(255, 107, 107, 0.8);\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-container\">\n        <canvas id=\"game-canvas\"></canvas>\n        <div class=\"ui-overlay\" id=\"ui-overlay\"></div>\n    </div>\n\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js\"></script>\n    <script>\n        /*\n         * SKY STRIKE - 3D Aerial Bombing Game\n         * \n         * Fly a fighter plane over a procedurally-generated city and drop bombs\n         * to destroy buildings. Score points for each hit. Endless arcade gameplay.\n         * \n         * Controls:\n         * - Mobile: Left joystick to steer, right button to drop bombs\n         * - Desktop: WASD/Arrows to steer, Space/Click to drop bombs\n         */\n\n        async function run(mode) {\n            const container = document.getElementById('game-container');\n            const canvas = document.getElementById('game-canvas');\n            const uiOverlay = document.getElementById('ui-overlay');\n\n            // ==================== CONSTANTS ====================\n            const WORLD_SIZE = 10000; // Effectively infinite\n            const CHUNK_SIZE = 100;\n            const BUILDING_GRID = 40;\n            const STREET_WIDTH = 15;\n            const PLANE_ALTITUDE = 150;\n            const BOMB_COOLDOWN = 300;\n            const CHUNK_LOAD_DISTANCE = 12; // Load chunks this many chunks away\n            const CHUNK_UNLOAD_DISTANCE = 15; // Unload chunks this many chunks away\n\n            // ==================== ASSET LOADING ====================\n            const assetCache = {\n                images: {},\n                audio: {}\n            };\n\n            let audioContext = null;\n\n            function initAudioContext() {\n                if (!audioContext) {\n                    audioContext = new (window.AudioContext || window.webkitAudioContext)();\n                }\n                return audioContext;\n            }\n\n            async function loadImage(id) {\n                const assetInfo = lib.getAsset(id);\n                if (!assetInfo || assetInfo.type !== 'image') return null;\n\n                return new Promise((resolve) => {\n                    const img = new Image();\n                    img.crossOrigin = 'anonymous';\n                    img.onload = () => {\n                        assetCache.images[id] = img;\n                        resolve(img);\n                    };\n                    img.onerror = () => {\n                        lib.log(`Failed to load image: ${id}`);\n                        resolve(null);\n                    };\n                    img.src = assetInfo.url;\n                });\n            }\n\n            async function loadAudio(id) {\n                const assetInfo = lib.getAsset(id);\n                if (!assetInfo || assetInfo.type !== 'audio') return null;\n\n                try {\n                    const response = await fetch(assetInfo.url);\n                    const arrayBuffer = await response.arrayBuffer();\n                    const ctx = initAudioContext();\n                    const audioBuffer = await ctx.decodeAudioData(arrayBuffer);\n                    assetCache.audio[id] = {\n                        buffer: audioBuffer,\n                        loop: assetInfo.loop === 'true'\n                    };\n                    return audioBuffer;\n                } catch (e) {\n                    lib.log(`Failed to load audio: ${id}`);\n                    return null;\n                }\n            }\n\n            function playSound(id, volume = 1.0) {\n                if (!audioContext || !assetCache.audio[id]) return null;\n\n                const source = audioContext.createBufferSource();\n                const gainNode = audioContext.createGain();\n\n                source.buffer = assetCache.audio[id].buffer;\n                source.loop = assetCache.audio[id].loop;\n                gainNode.gain.value = volume;\n\n                source.connect(gainNode);\n                gainNode.connect(audioContext.destination);\n                source.start(0);\n\n                return { source, gainNode };\n            }\n\n            // Preload all assets\n            await Promise.all([\n                loadImage('fighter_plane'),\n                loadImage('bomb_projectile'),\n                loadImage('explosion_effect'),\n                loadAudio('bomb_drop_sound'),\n                loadAudio('explosion_sound'),\n                loadAudio('engine_loop')\n            ]);\n\n            // ==================== THREE.JS SETUP ====================\n            const scene = new THREE.Scene();\n\n            // Sky gradient\n            const skyColor1 = new THREE.Color(0x87CEEB);\n            const skyColor2 = new THREE.Color(0x1a1a2e);\n            scene.background = new THREE.Color(0x4a90d9);\n\n            // Camera\n            const camera = new THREE.PerspectiveCamera(60, canvas.clientWidth / canvas.clientHeight, 1, 2000);\n\n            // Renderer\n            const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });\n            renderer.setSize(canvas.clientWidth, canvas.clientHeight);\n            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));\n\n            // Lighting\n            const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);\n            scene.add(ambientLight);\n\n            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);\n            directionalLight.position.set(100, 200, 100);\n            scene.add(directionalLight);\n\n            // ==================== GAME STATE ====================\n            const config = window.gameConfig;\n\n            // Runtime state (not in gameConfig)\n            let score = 0;\n            let coinsCollected = 0;\n            let bombsRemaining = config.bombCount;\n            let turretAmmoRemaining = config.turretAmmo;\n            let lastBombTime = 0;\n            let lastNukeTime = 0;\n            const NUKE_COOLDOWN = 5000; // 5 seconds\n            let engineSound = null;\n            let rainbowColorTime = 0;\n\n            // Player state\n            const player = {\n                x: 0,\n                y: PLANE_ALTITUDE,\n                z: 0,\n                rotation: 0,\n                pitch: 0,\n                roll: 0,\n                velocityX: 0,\n                velocityZ: 0\n            };\n\n            // Input state\n            const input = {\n                moveX: 0,\n                moveY: 0,\n                dropBomb: false,\n                boosting: false,\n                joystickActive: false,\n                joystickStartX: 0,\n                joystickStartY: 0,\n                joystickTouchId: null,\n                mouseX: 0,\n                mouseY: 0,\n                cameraYaw: 0,\n                cameraPitch: 0,\n                turretFiring: false,\n                lastTurretFireTime: 0\n            };\n\n            // Game objects\n            const buildings = [];\n            const bombs = [];\n            const explosions = [];\n            const jetStreamParticles = [];\n            const coins = [];\n            const aiPlanes = [];\n            const bullets = [];\n            let groundMesh = null;\n            let planeMesh = null;\n            let lockedOnPlane = null;\n\n            // ==================== BUILDING COLORS ====================\n            const buildingColors = [\n                0x4a4a4a, // Dark gray\n                0x6b6b6b, // Medium gray\n                0x8b7355, // Brown\n                0x5c5c5c, // Steel gray\n                0x3d5c5c, // Teal gray\n                0x4a5568, // Slate\n                0x718096, // Cool gray\n                0x2d3748  // Dark slate\n            ];\n\n            // ==================== CREATE GAME OBJECTS ====================\n\n            // Ground plane\n            function createGround() {\n                const groundGeometry = new THREE.PlaneGeometry(WORLD_SIZE * 4, WORLD_SIZE * 4);\n                const groundMaterial = new THREE.MeshBasicMaterial({ \n                    color: 0x2d2d2d,\n                    side: THREE.DoubleSide\n                });\n                groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);\n                groundMesh.rotation.x = -Math.PI / 2;\n                groundMesh.position.y = 0;\n                scene.add(groundMesh);\n            }\n\n            // Create starfield texture for Deep Space skin\n            function createStarfieldTexture() {\n                const canvas = document.createElement('canvas');\n                canvas.width = 512;\n                canvas.height = 512;\n                const ctx = canvas.getContext('2d');\n\n                // Dark space background\n                ctx.fillStyle = '#0a0e27';\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n                // Draw massive bright white stars - HUGE and very visible\n                ctx.fillStyle = '#ffffff';\n                for (let i = 0; i < 600; i++) {\n                    const x = Math.random() * canvas.width;\n                    const y = Math.random() * canvas.height;\n                    const size = Math.random() * 16 + 8; // MASSIVE stars: 8-24px\n                    const opacity = Math.random() * 0.2 + 0.8; // Very bright: 0.8-1.0\n\n                    ctx.globalAlpha = opacity;\n                    ctx.fillRect(x, y, size, size);\n                }\n\n                // Add extra bright mega star clusters for maximum visibility\n                ctx.fillStyle = '#ffffff';\n                for (let i = 0; i < 150; i++) {\n                    const x = Math.random() * canvas.width;\n                    const y = Math.random() * canvas.height;\n                    const size = Math.random() * 12 + 10; // Extra large: 10-22px\n                    ctx.globalAlpha = 1.0; // Full opacity for maximum brightness\n                    ctx.fillRect(x, y, size, size);\n                }\n\n                // Add massive glowing halos around stars for extra pop and visibility\n                ctx.fillStyle = '#ffffff';\n                for (let i = 0; i < 120; i++) {\n                    const x = Math.random() * canvas.width;\n                    const y = Math.random() * canvas.height;\n                    \n                    // Draw large glow halo\n                    const gradient = ctx.createRadialGradient(x, y, 0, x, y, 24);\n                    gradient.addColorStop(0, 'rgba(255, 255, 255, 0.8)');\n                    gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.3)');\n                    gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');\n                    ctx.fillStyle = gradient;\n                    ctx.fillRect(x - 24, y - 24, 48, 48);\n                    \n                    // Draw bright core - much larger\n                    ctx.fillStyle = '#ffffff';\n                    ctx.globalAlpha = 1.0;\n                    ctx.fillRect(x - 4, y - 4, 8, 8);\n                }\n\n                // Add some super bright point stars scattered throughout\n                ctx.fillStyle = '#ffffff';\n                for (let i = 0; i < 200; i++) {\n                    const x = Math.random() * canvas.width;\n                    const y = Math.random() * canvas.height;\n                    const size = Math.random() * 6 + 4; // Medium-large: 4-10px\n                    ctx.globalAlpha = 1.0;\n                    ctx.fillRect(x, y, size, size);\n                }\n\n                ctx.globalAlpha = 1.0;\n\n                const texture = new THREE.CanvasTexture(canvas);\n                texture.magFilter = THREE.LinearFilter;\n                texture.minFilter = THREE.LinearFilter;\n                return texture;\n            }\n\n            // Create a generic plane mesh (for player or AI)\n            function createPlaneMesh(bodyColor, wingColor, finColor, isDeepSpace = false) {\n                const planeGroup = new THREE.Group();\n\n                // Fuselage\n                const fuselageGeometry = new THREE.BoxGeometry(4, 2, 15);\n                let fuselageMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    fuselageMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: bodyColor });\n                } else {\n                    fuselageMaterial = new THREE.MeshLambertMaterial({ color: bodyColor });\n                }\n                const fuselage = new THREE.Mesh(fuselageGeometry, fuselageMaterial);\n                fuselage.name = 'fuselage';\n                planeGroup.add(fuselage);\n\n                // Wings\n                const wingGeometry = new THREE.BoxGeometry(20, 0.5, 6);\n                let wingMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    wingMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: wingColor });\n                } else {\n                    wingMaterial = new THREE.MeshLambertMaterial({ color: wingColor });\n                }\n                const wings = new THREE.Mesh(wingGeometry, wingMaterial);\n                wings.position.z = 2;\n                wings.name = 'wings';\n                planeGroup.add(wings);\n\n                // Tail\n                const tailGeometry = new THREE.BoxGeometry(0.5, 4, 3);\n                let tailMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    tailMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: bodyColor });\n                } else {\n                    tailMaterial = new THREE.MeshLambertMaterial({ color: bodyColor });\n                }\n                const tail = new THREE.Mesh(tailGeometry, tailMaterial);\n                tail.position.z = 6;\n                tail.position.y = 2;\n                tail.name = 'tail';\n                planeGroup.add(tail);\n\n                // Horizontal stabilizer\n                const stabilizerGeometry = new THREE.BoxGeometry(8, 0.3, 2);\n                let stabilizerMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    stabilizerMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: bodyColor });\n                } else {\n                    stabilizerMaterial = new THREE.MeshLambertMaterial({ color: bodyColor });\n                }\n                const stabilizer = new THREE.Mesh(stabilizerGeometry, stabilizerMaterial);\n                stabilizer.position.z = 6;\n                stabilizer.position.y = 0.5;\n                stabilizer.name = 'stabilizer';\n                planeGroup.add(stabilizer);\n\n                // Cockpit\n                const cockpitGeometry = new THREE.BoxGeometry(2, 1.5, 3);\n                let cockpitMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    cockpitMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: 0x0a0e27 });\n                } else {\n                    cockpitMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });\n                }\n                const cockpit = new THREE.Mesh(cockpitGeometry, cockpitMaterial);\n                cockpit.position.y = 1.5;\n                cockpit.position.z = -2;\n                planeGroup.add(cockpit);\n\n                // Nose\n                const noseGeometry = new THREE.ConeGeometry(1.5, 4, 4);\n                let noseMaterial;\n                if (isDeepSpace) {\n                    const starTexture = createStarfieldTexture();\n                    noseMaterial = new THREE.MeshLambertMaterial({ map: starTexture, color: 0x0a0e27 });\n                } else {\n                    noseMaterial = new THREE.MeshLambertMaterial({ color: 0xcc0000 });\n                }\n                const nose = new THREE.Mesh(noseGeometry, noseMaterial);\n                nose.rotation.x = Math.PI / 2;\n                nose.position.z = -9;\n                planeGroup.add(nose);\n\n                return planeGroup;\n            }\n\n            // Player plane\n            function createPlane() {\n                // Get current skin\n                const currentSkinObj = PLANE_SKINS.find(s => s.id === config.currentSkin) || PLANE_SKINS[0];\n                \n                // Determine colors based on skin type\n                let bodyColor, wingColor, finColor;\n                if (currentSkinObj.colors) {\n                    // Multi-color skin (like camo)\n                    bodyColor = currentSkinObj.colors.body;\n                    wingColor = currentSkinObj.colors.wings;\n                    finColor = currentSkinObj.colors.fins;\n                } else if (currentSkinObj.isRainbow) {\n                    // Rainbow skin - use initial color, will be updated in game loop\n                    bodyColor = wingColor = finColor = 0xff00ff;\n                } else if (currentSkinObj.isDeepSpace) {\n                    // Deep Space skin - dark blue/purple with starfield\n                    bodyColor = 0x0a0e27;\n                    wingColor = 0x0f1535;\n                    finColor = 0x0a0e27;\n                } else {\n                    // Single color skin\n                    bodyColor = wingColor = finColor = currentSkinObj.color;\n                }\n\n                planeMesh = createPlaneMesh(bodyColor, wingColor, finColor, currentSkinObj.isDeepSpace);\n                scene.add(planeMesh);\n            }\n\n            // Create an AI plane\n            function createAIPlane(x, z) {\n                // Random AI plane color (different from player)\n                const aiColors = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff, 0xff8800];\n                const bodyColor = aiColors[Math.floor(Math.random() * aiColors.length)];\n                const wingColor = bodyColor;\n                const finColor = bodyColor;\n\n                const mesh = createPlaneMesh(bodyColor, wingColor, finColor, false);\n                mesh.scale.setScalar(0.7); // Make AI planes slightly smaller\n                mesh.position.set(x, PLANE_ALTITUDE + Math.random() * 50, z);\n                scene.add(mesh);\n\n                // Generate random waypoints for patrol\n                const waypoints = [];\n                for (let i = 0; i < 4; i++) {\n                    waypoints.push({\n                        x: x + (Math.random() - 0.5) * 400,\n                        z: z + (Math.random() - 0.5) * 400\n                    });\n                }\n\n                // Create health bar\n                const healthBarContainer = document.createElement('div');\n                healthBarContainer.style.cssText = `\n                    position: absolute;\n                    width: 60px;\n                    height: 8px;\n                    background: rgba(0, 0, 0, 0.5);\n                    border: 1px solid rgba(255, 255, 255, 0.5);\n                    border-radius: 4px;\n                    display: none;\n                    z-index: 40;\n                `;\n                \n                const healthBar = document.createElement('div');\n                healthBar.style.cssText = `\n                    width: 100%;\n                    height: 100%;\n                    background: linear-gradient(90deg, #00ff00, #ffff00);\n                    border-radius: 3px;\n                    transition: width 0.1s;\n                `;\n                healthBarContainer.appendChild(healthBar);\n                uiOverlay.appendChild(healthBarContainer);\n\n                return {\n                    mesh,\n                    x,\n                    z,\n                    y: PLANE_ALTITUDE + Math.random() * 50,\n                    rotation: Math.random() * Math.PI * 2,\n                    pitch: 0,\n                    roll: 0,\n                    speed: config.planeSpeed * (0.6 + Math.random() * 0.4), // 60-100% of player speed\n                    waypoints,\n                    currentWaypoint: 0,\n                    bodyColor,\n                    wingColor,\n                    finColor,\n                    health: 10,\n                    maxHealth: 10,\n                    healthBarContainer,\n                    healthBar\n                };\n            }\n\n            // Update AI planes\n            function updateAIPlanes(deltaTime) {\n                // Spawn new AI planes if needed\n                if (aiPlanes.length < config.aiPlaneCount) {\n                    // Spawn around player but not too close\n                    const angle = Math.random() * Math.PI * 2;\n                    const distance = 300 + Math.random() * 200;\n                    const spawnX = player.x + Math.cos(angle) * distance;\n                    const spawnZ = player.z + Math.sin(angle) * distance;\n                    \n                    aiPlanes.push(createAIPlane(spawnX, spawnZ));\n                }\n\n                // Update each AI plane\n                for (let i = aiPlanes.length - 1; i >= 0; i--) {\n                    const ai = aiPlanes[i];\n\n                    // Get current waypoint\n                    const waypoint = ai.waypoints[ai.currentWaypoint];\n                    const dx = waypoint.x - ai.x;\n                    const dz = waypoint.z - ai.z;\n                    const distance = Math.sqrt(dx * dx + dz * dz);\n\n                    // Switch to next waypoint if close enough\n                    if (distance < 50) {\n                        ai.currentWaypoint = (ai.currentWaypoint + 1) % ai.waypoints.length;\n                    }\n\n                    // Calculate desired rotation to face waypoint\n                    const desiredRotation = Math.atan2(dx, -dz);\n                    \n                    // Smoothly turn towards waypoint\n                    let rotationDiff = desiredRotation - ai.rotation;\n                    // Normalize angle difference to [-PI, PI]\n                    while (rotationDiff > Math.PI) rotationDiff -= Math.PI * 2;\n                    while (rotationDiff < -Math.PI) rotationDiff += Math.PI * 2;\n                    \n                    ai.rotation += rotationDiff * 0.5 * deltaTime;\n\n                    // Bank (roll) based on turning\n                    const targetRoll = Math.max(-0.5, Math.min(0.5, rotationDiff * 0.3));\n                    ai.roll += (targetRoll - ai.roll) * 8 * deltaTime;\n\n                    // Move forward\n                    ai.x -= Math.sin(ai.rotation) * ai.speed * deltaTime;\n                    ai.z -= Math.cos(ai.rotation) * ai.speed * deltaTime;\n\n                    // Slight altitude variation\n                    const targetAltitude = PLANE_ALTITUDE + 30 + Math.sin(ai.x * 0.01) * 20;\n                    ai.y += (targetAltitude - ai.y) * 0.5 * deltaTime;\n\n                    // Update mesh\n                    ai.mesh.position.set(ai.x, ai.y, ai.z);\n                    ai.mesh.rotation.set(ai.pitch, ai.rotation, ai.roll);\n\n                    // Update health bar position and visibility\n                    const vector = new THREE.Vector3(ai.x, ai.y + 15, ai.z);\n                    vector.project(camera);\n                    const canvas = document.getElementById('game-canvas');\n                    const screenX = (vector.x * 0.5 + 0.5) * canvas.clientWidth;\n                    const screenY = (-vector.y * 0.5 + 0.5) * canvas.clientHeight;\n\n                    if (ai.healthBarContainer) {\n                        ai.healthBarContainer.style.left = (screenX - 30) + 'px';\n                        ai.healthBarContainer.style.top = (screenY - 20) + 'px';\n                        \n                        // Show health bar if damaged or locked on\n                        const isLocked = lockedOnPlane === ai;\n                        const isDamaged = ai.health < ai.maxHealth;\n                        ai.healthBarContainer.style.display = (isLocked || isDamaged) ? 'block' : 'none';\n                        \n                        // Update health bar width\n                        const healthPercent = (ai.health / ai.maxHealth) * 100;\n                        ai.healthBar.style.width = healthPercent + '%';\n                        \n                        // Change color based on health\n                        if (healthPercent > 60) {\n                            ai.healthBar.style.background = 'linear-gradient(90deg, #00ff00, #ffff00)';\n                        } else if (healthPercent > 30) {\n                            ai.healthBar.style.background = 'linear-gradient(90deg, #ffff00, #ff6600)';\n                        } else {\n                            ai.healthBar.style.background = 'linear-gradient(90deg, #ff6600, #ff0000)';\n                        }\n                    }\n\n                    // Remove if too far from player\n                    const distToPlayer = Math.sqrt(\n                        Math.pow(ai.x - player.x, 2) + \n                        Math.pow(ai.z - player.z, 2)\n                    );\n                    if (distToPlayer > 1000) {\n                        scene.remove(ai.mesh);\n                        if (ai.healthBarContainer) {\n                            ai.healthBarContainer.remove();\n                        }\n                        aiPlanes.splice(i, 1);\n                    }\n                }\n            }\n\n            // Create a building\n            function createBuilding(x, z, height, color) {\n                const width = 20 + Math.random() * 15;\n                const depth = 20 + Math.random() * 15;\n\n                const geometry = new THREE.BoxGeometry(width, height, depth);\n                const material = new THREE.MeshLambertMaterial({ color });\n                const mesh = new THREE.Mesh(geometry, material);\n\n                mesh.position.set(x, height / 2, z);\n                scene.add(mesh);\n\n                return {\n                    mesh,\n                    x,\n                    z,\n                    width,\n                    depth,\n                    height,\n                    destroyed: false\n                };\n            }\n\n            // Seeded random for procedural generation\n            function seededRandom(seed) {\n                const x = Math.sin(seed) * 10000;\n                return x - Math.floor(x);\n            }\n\n            // Generate city chunk with seeded randomness\n            function generateCityChunk(chunkX, chunkZ) {\n                const startX = chunkX * CHUNK_SIZE;\n                const endX = startX + CHUNK_SIZE;\n                const startZ = chunkZ * CHUNK_SIZE;\n                const endZ = startZ + CHUNK_SIZE;\n\n                for (let x = startX; x < endX; x += BUILDING_GRID) {\n                    for (let z = startZ; z < endZ; z += BUILDING_GRID) {\n                        // Use seeded random based on position\n                        const seed1 = x * 73856093 ^ z * 19349663;\n                        const seed2 = seed1 * 83492791;\n                        const seed3 = seed2 * 73856093;\n\n                        // Random chance to place building based on density\n                        if (seededRandom(seed1) < config.buildingDensity) {\n                            // Add some randomness to position (seeded)\n                            const offsetX = (seededRandom(seed2) - 0.5) * 10;\n                            const offsetZ = (seededRandom(seed3) - 0.5) * 10;\n\n                            const height = 20 + seededRandom(seed1 * 2) * config.maxBuildingHeight;\n                            const colorIndex = Math.floor(seededRandom(seed2 * 2) * buildingColors.length);\n                            const color = buildingColors[colorIndex];\n\n                            const building = createBuilding(\n                                x + offsetX,\n                                z + offsetZ,\n                                height,\n                                color\n                            );\n                            buildings.push(building);\n                        }\n                    }\n                }\n            }\n\n            // Create a coin\n            function createCoin(x, z) {\n                const coinGroup = new THREE.Group();\n\n                // Coin body - flat cylinder\n                const coinGeometry = new THREE.CylinderGeometry(3, 3, 0.5, 32);\n                const coinMaterial = new THREE.MeshLambertMaterial({ color: 0xffd700 });\n                const coinMesh = new THREE.Mesh(coinGeometry, coinMaterial);\n                coinGroup.add(coinMesh);\n\n                // Coin edge highlight\n                const edgeGeometry = new THREE.TorusGeometry(3, 0.3, 8, 32);\n                const edgeMaterial = new THREE.MeshLambertMaterial({ color: 0xffed4e });\n                const edge = new THREE.Mesh(edgeGeometry, edgeMaterial);\n                edge.rotation.x = Math.PI / 2;\n                edge.position.y = 0.3;\n                coinGroup.add(edge);\n\n                coinGroup.position.set(x, 5, z);\n                scene.add(coinGroup);\n\n                return {\n                    mesh: coinGroup,\n                    x,\n                    z,\n                    collected: false,\n                    rotation: 0\n                };\n            }\n\n            // Create bomb\n            function createBomb() {\n                const bombGroup = new THREE.Group();\n\n                // Bomb body\n                const bodyGeometry = new THREE.CylinderGeometry(1, 1, 4, 8);\n                const bodyMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });\n                const body = new THREE.Mesh(bodyGeometry, bodyMaterial);\n                body.rotation.x = Math.PI / 2;\n                bombGroup.add(body);\n\n                // Nose cone\n                const noseGeometry = new THREE.ConeGeometry(1, 2, 8);\n                const noseMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });\n                const nose = new THREE.Mesh(noseGeometry, noseMaterial);\n                nose.rotation.x = Math.PI / 2;\n                nose.position.z = -3;\n                bombGroup.add(nose);\n\n                // Fins\n                const finGeometry = new THREE.BoxGeometry(0.2, 2, 1);\n                const finMaterial = new THREE.MeshLambertMaterial({ color: 0x222222 });\n\n                for (let i = 0; i < 4; i++) {\n                    const fin = new THREE.Mesh(finGeometry, finMaterial);\n                    fin.position.z = 2;\n                    fin.rotation.z = (i * Math.PI) / 2;\n                    fin.position.x = Math.cos((i * Math.PI) / 2) * 1.5;\n                    fin.position.y = Math.sin((i * Math.PI) / 2) * 1.5;\n                    bombGroup.add(fin);\n                }\n\n                // Yellow stripe\n                const stripeGeometry = new THREE.CylinderGeometry(1.1, 1.1, 0.5, 8);\n                const stripeMaterial = new THREE.MeshLambertMaterial({ color: 0xffcc00 });\n                const stripe = new THREE.Mesh(stripeGeometry, stripeMaterial);\n                stripe.rotation.x = Math.PI / 2;\n                bombGroup.add(stripe);\n\n                bombGroup.scale.setScalar(config.bombSize);\n\n                return bombGroup;\n            }\n\n            // Create explosion\n            function createExplosion(x, y, z, bombType = 'normal') {\n                const explosionGroup = new THREE.Group();\n\n                // Multiple spheres for explosion effect\n                const colors = [0xff4400, 0xff8800, 0xffcc00, 0xff0000];\n\n                if (bombType === 'nuke') {\n                    // Mushroom cloud effect for nukes - scaled to 350 unit radius\n                    const nukeScale = 350 / 100; // Scale factor to reach 350 units\n                    \n                    // Wide base - massive horizontal spread at ground level\n                    for (let i = 0; i < 20; i++) {\n                        const size = (12 + Math.random() * 18) * nukeScale;\n                        const geometry = new THREE.SphereGeometry(size, 8, 8);\n                        const material = new THREE.MeshBasicMaterial({\n                            color: colors[Math.floor(Math.random() * colors.length)],\n                            transparent: true,\n                            opacity: 0.85\n                        });\n                        const sphere = new THREE.Mesh(geometry, material);\n\n                        // Spread out in a wide circle at the base - scaled to 350\n                        const angle = Math.random() * Math.PI * 2;\n                        const radius = Math.random() * 100 * nukeScale;\n                        const height = (Math.random() - 0.5) * 15 * nukeScale;\n\n                        sphere.position.set(\n                            Math.cos(angle) * radius,\n                            height,\n                            Math.sin(angle) * radius\n                        );\n\n                        explosionGroup.add(sphere);\n                    }\n\n                    // Stem - vertical column of fire\n                    for (let i = 0; i < 6; i++) {\n                        const size = (8 + Math.random() * 12) * nukeScale;\n                        const geometry = new THREE.SphereGeometry(size, 8, 8);\n                        const material = new THREE.MeshBasicMaterial({\n                            color: colors[Math.floor(Math.random() * colors.length)],\n                            transparent: true,\n                            opacity: 0.85\n                        });\n                        const sphere = new THREE.Mesh(geometry, material);\n\n                        sphere.position.set(\n                            (Math.random() - 0.5) * 8 * nukeScale,\n                            (Math.random() - 0.5) * 8 * nukeScale + i * 15 * nukeScale,\n                            (Math.random() - 0.5) * 8 * nukeScale\n                        );\n\n                        explosionGroup.add(sphere);\n                    }\n\n                    // Cap - large mushroom top\n                    for (let i = 0; i < 12; i++) {\n                        const size = (10 + Math.random() * 15) * nukeScale;\n                        const geometry = new THREE.SphereGeometry(size, 8, 8);\n                        const material = new THREE.MeshBasicMaterial({\n                            color: colors[Math.floor(Math.random() * colors.length)],\n                            transparent: true,\n                            opacity: 0.8\n                        });\n                        const sphere = new THREE.Mesh(geometry, material);\n\n                        // Spread out in a dome shape at the top - wider horizontal spread, scaled to 350\n                        const angle = Math.random() * Math.PI * 2;\n                        const radius = Math.random() * 70 * nukeScale;\n                        const height = (60 + Math.random() * 20) * nukeScale;\n\n                        sphere.position.set(\n                            Math.cos(angle) * radius,\n                            height,\n                            Math.sin(angle) * radius\n                        );\n\n                        explosionGroup.add(sphere);\n                    }\n                } else {\n                    // Normal explosion - smaller, more compact\n                    for (let i = 0; i < 8; i++) {\n                        const size = 5 + Math.random() * 10;\n                        const geometry = new THREE.SphereGeometry(size, 8, 8);\n                        const material = new THREE.MeshBasicMaterial({\n                            color: colors[Math.floor(Math.random() * colors.length)],\n                            transparent: true,\n                            opacity: 0.8\n                        });\n                        const sphere = new THREE.Mesh(geometry, material);\n\n                        sphere.position.set(\n                            (Math.random() - 0.5) * 15,\n                            (Math.random() - 0.5) * 15,\n                            (Math.random() - 0.5) * 15\n                        );\n\n                        explosionGroup.add(sphere);\n                    }\n                }\n\n                explosionGroup.position.set(x, y, z);\n                scene.add(explosionGroup);\n\n                return {\n                    mesh: explosionGroup,\n                    life: 1.0,\n                    x, y, z,\n                    type: bombType\n                };\n            }\n\n            // ==================== UI CREATION ====================\n            function createUI() {\n                uiOverlay.innerHTML = '';\n\n                if (mode === 'edit') {\n                    uiOverlay.innerHTML = `\n                        <div class=\"edit-overlay\">\n                            <h2>\u2708\ufe0f EDIT MODE</h2>\n                            <p>Use the settings panel to customize your game</p>\n                        </div>\n                    `;\n                    return;\n                }\n\n                // Show control selection modal if not yet chosen\n                if (!config.controlMode || config.controlMode === 'null') {\n                    uiOverlay.innerHTML = `\n                        <div id=\"control-select-modal\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.95); z-index: 2000; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: var(--space-lg);\">\n                            <div style=\"background: linear-gradient(135deg, rgba(20, 30, 48, 0.95) 0%, rgba(36, 59, 85, 0.95) 100%); border: 2px solid rgba(100, 180, 255, 0.4); border-radius: 16px; padding: var(--space-lg); text-align: center; color: #fff;\">\n                                <h2 style=\"margin-bottom: var(--space-md); color: #ffd700; font-size: 28px;\">\ud83c\udfae SELECT CONTROL MODE</h2>\n                                <p style=\"margin-bottom: var(--space-lg); font-size: 16px; opacity: 0.9;\">How are you playing?</p>\n                                <div style=\"display: flex; gap: var(--space-md); justify-content: center;\">\n                                    <button id=\"mobile-mode-btn\" style=\"padding: var(--space-md) var(--space-lg); background: linear-gradient(135deg, #0099ff 0%, #0066ff 100%); border: 2px solid rgba(255, 255, 255, 0.4); border-radius: 8px; color: #fff; font-size: 18px; font-weight: 700; cursor: pointer; font-family: 'Orbitron', sans-serif; transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); box-shadow: 0 4px 12px rgba(0, 150, 255, 0.4);\">\ud83d\udcf1 MOBILE</button>\n                                    <button id=\"desktop-mode-btn\" style=\"padding: var(--space-md) var(--space-lg); background: linear-gradient(135deg, #ff6600 0%, #ff4400 100%); border: 2px solid rgba(255, 255, 255, 0.4); border-radius: 8px; color: #fff; font-size: 18px; font-weight: 700; cursor: pointer; font-family: 'Orbitron', sans-serif; transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); box-shadow: 0 4px 12px rgba(255, 100, 0, 0.4);\">\ud83d\udda5\ufe0f DESKTOP</button>\n                                </div>\n                            </div>\n                        </div>\n                    `;\n                    \n                    // Setup control mode selection\n                    const mobileBtn = document.getElementById('mobile-mode-btn');\n                    const desktopBtn = document.getElementById('desktop-mode-btn');\n                    \n                    const selectControlMode = (mode) => {\n                        config.controlMode = mode;\n                        const modal = document.getElementById('control-select-modal');\n                        if (modal) modal.remove();\n                        createUI(); // Recreate UI with selected mode\n                    };\n                    \n                    if (mobileBtn) {\n                        mobileBtn.addEventListener('click', () => selectControlMode('mobile'));\n                        mobileBtn.addEventListener('mouseover', () => mobileBtn.style.transform = 'scale(1.05)');\n                        mobileBtn.addEventListener('mouseout', () => mobileBtn.style.transform = 'scale(1)');\n                    }\n                    if (desktopBtn) {\n                        desktopBtn.addEventListener('click', () => selectControlMode('desktop'));\n                        desktopBtn.addEventListener('mouseover', () => desktopBtn.style.transform = 'scale(1.05)');\n                        desktopBtn.addEventListener('mouseout', () => desktopBtn.style.transform = 'scale(1)');\n                    }\n                    \n                    return;\n                }\n\n                uiOverlay.innerHTML = `\n                    <div class=\"hud\">\n                        <div class=\"hud-panel score-display\">\n                            <span class=\"hud-icon\">\ud83c\udfaf</span>\n                            <div>\n                                <div class=\"hud-label\">Score</div>\n                                <div class=\"hud-value\" id=\"score-value\">0</div>\n                            </div>\n                        </div>\n                        <div class=\"hud-panel bomb-display\">\n                            <span class=\"hud-icon\">\ud83d\udca3</span>\n                            <div>\n                                <div class=\"hud-label\">Bombs</div>\n                                <div class=\"hud-value\" id=\"bomb-value\">${bombsRemaining === -1 ? '\u221e' : bombsRemaining}</div>\n                            </div>\n                        </div>\n                        <div class=\"hud-panel coin-display\">\n                            <span class=\"hud-icon\">\ud83e\ude99</span>\n                            <div>\n                                <div class=\"hud-label\">Coins</div>\n                                <div class=\"hud-value\" id=\"coin-value\">0</div>\n                            </div>\n                        </div>\n\n                    </div>\n                    <div class=\"crosshair\" id=\"crosshair\">\n                        <div class=\"crosshair-circle\"></div>\n                        <div class=\"crosshair-dot\"></div>\n                    </div>\n                    <div class=\"joystick-zone\" id=\"joystick-zone\">\n                        <div class=\"joystick-base\" id=\"joystick-base\">\n                            <div class=\"joystick-stick\" id=\"joystick-stick\"></div>\n                        </div>\n                    </div>\n                    <!-- Desktop Controls Info -->\n                    <div id=\"nuke-cooldown-display\" style=\"position: absolute; bottom: var(--space-lg); left: calc(80px + var(--space-lg) + var(--space-sm)); background: linear-gradient(135deg, rgba(20, 30, 48, 0.9) 0%, rgba(36, 59, 85, 0.85) 100%); border: 2px solid rgba(255, 170, 0, 0.4); border-radius: 12px; padding: 12px 16px; color: #fff; font-size: 28px; font-weight: 700; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1); display: none; flex-direction: column; align-items: center; justify-content: center; min-width: 80px;\">\n                        <div style=\"color: #ffaa00; font-size: 32px; margin-bottom: 4px;\">\u2622\ufe0f</div>\n                        <div id=\"nuke-countdown-value\" style=\"color: #ffaa00; text-shadow: 0 0 12px rgba(255, 170, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.8);\">5</div>\n                    </div>\n\n                    <div id=\"controls-panel\" style=\"position: absolute; right: var(--space-lg); bottom: calc(var(--space-lg) + 48px); background: linear-gradient(135deg, rgba(20, 30, 48, 0.9) 0%, rgba(36, 59, 85, 0.85) 100%); border: 2px solid rgba(100, 180, 255, 0.4); border-radius: 12px; padding: 12px 16px; color: #fff; font-size: 14px; font-weight: 700; text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1); text-align: left; line-height: 1.6; letter-spacing: 0.5px; display: none;\">\n                        <div style=\"color: #ffd700; margin-bottom: 4px; font-size: 12px; text-transform: uppercase;\">CONTROLS</div>\n                        <div>A/D - TURN</div>\n                        <div>W/S - ELEVATION</div>\n                        <div>\ud83d\udca3 BOMB - LEFT CLICK</div>\n                        <div>\u2622\ufe0f NUKE - SPACE BAR</div>\n                        <div>\ud83d\udd2b TURRET - RIGHT CLICK</div>\n                        <div>\u26a1 BOOST - SHIFT</div>\n                    </div>\n\n                    <!-- Mobile Control Buttons -->\n                    <div id=\"mobile-controls\" style=\"position: absolute; bottom: var(--space-lg); right: var(--space-lg); display: flex; flex-direction: column; gap: 8px; z-index: 90;\">\n                        <!-- Top row: Nuke and Turret -->\n                        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n                            <button id=\"nuke-button\" style=\"width: 80px; height: 80px; background: radial-gradient(circle at 30% 30%, #ffaa00 0%, #ff8800 50%, #ff6600 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(255, 136, 0, 0.5), 0 0 30px rgba(255, 170, 0, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"NUKE\">\u2622\ufe0f</button>\n                            <button id=\"turret-button\" style=\"width: 80px; height: 80px; background: radial-gradient(circle at 30% 30%, #00ff00 0%, #00cc00 50%, #009900 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0, 255, 0, 0.5), 0 0 30px rgba(0, 200, 0, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"TURRET\">\ud83d\udd2b</button>\n                        </div>\n                        <!-- Middle row: Up and Down arrows -->\n                        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n                            <button id=\"up-button\" style=\"width: 70px; height: 70px; background: radial-gradient(circle at 30% 30%, #00ccff 0%, #0099ff 50%, #0066ff 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0, 200, 255, 0.5), 0 0 30px rgba(0, 150, 200, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"UP\">\u2b06\ufe0f</button>\n                            <button id=\"down-button\" style=\"width: 70px; height: 70px; background: radial-gradient(circle at 30% 30%, #00ccff 0%, #0099ff 50%, #0066ff 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0, 200, 255, 0.5), 0 0 30px rgba(0, 150, 200, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"DOWN\">\u2b07\ufe0f</button>\n                        </div>\n                        <!-- Bottom row: Bomb, Boost, Left, Right -->\n                        <div style=\"display: flex; gap: 8px; justify-content: flex-end;\">\n                            <button id=\"left-button\" style=\"width: 70px; height: 70px; background: radial-gradient(circle at 30% 30%, #0099ff 0%, #0066ff 50%, #0044cc 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0, 150, 255, 0.5), 0 0 30px rgba(0, 100, 200, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"LEFT\">\u25c0</button>\n                            <button id=\"boost-button\" style=\"width: 70px; height: 70px; background: radial-gradient(circle at 30% 30%, #ff00ff 0%, #cc00ff 50%, #9900ff 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(255, 0, 255, 0.5), 0 0 30px rgba(200, 0, 200, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"BOOST\">\u26a1</button>\n                            <button id=\"right-button\" style=\"width: 70px; height: 70px; background: radial-gradient(circle at 30% 30%, #0099ff 0%, #0066ff 50%, #0044cc 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 28px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0, 150, 255, 0.5), 0 0 30px rgba(0, 100, 200, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700; touch-action: manipulation;\" title=\"RIGHT\">\u25b6</button>\n                            <button id=\"bomb-button\" style=\"width: 80px; height: 80px; background: radial-gradient(circle at 30% 30%, #ff5555 0%, #cc0000 50%, #990000 100%); border: 4px solid rgba(255, 255, 255, 0.6); border-radius: 50%; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); cursor: pointer; transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); pointer-events: auto; box-shadow: 0 6px 20px rgba(200, 0, 0, 0.5), 0 0 30px rgba(255, 50, 50, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); font-family: 'Orbitron', sans-serif; font-weight: 700; font-size: 32px; touch-action: manipulation;\" title=\"BOMB\">\ud83d\udca3</button>\n                        </div>\n                    </div>\n\n                    <button id=\"shop-button\" style=\"position: absolute; bottom: var(--space-lg); left: var(--space-lg); width: 80px; height: 80px; background: radial-gradient(circle at 30% 30%, #9d4edd 0%, #7209b7 50%, #560bad 100%); border: 3px solid rgba(255, 255, 255, 0.6); border-radius: 12px; color: #fff; font-size: 36px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(114, 9, 183, 0.5), 0 0 30px rgba(157, 78, 221, 0.3), inset 0 -4px 8px rgba(0, 0, 0, 0.3); transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out); font-family: 'Orbitron', sans-serif; font-weight: 700;\">\ud83d\udecd\ufe0f</button>\n                    <div id=\"shop-modal\" style=\"display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); z-index: 1000; flex-direction: column; align-items: center; justify-content: center; padding: var(--space-lg);\">\n                        <div style=\"background: linear-gradient(135deg, rgba(20, 30, 48, 0.95) 0%, rgba(36, 59, 85, 0.95) 100%); border: 2px solid rgba(100, 180, 255, 0.4); border-radius: 16px; padding: var(--space-lg); max-width: 90%; max-height: 80%; overflow-y: auto; color: #fff;\">\n                            <h2 style=\"text-align: center; margin-bottom: var(--space-md); color: #ffd700; font-size: 28px;\">\ud83d\udecd\ufe0f PLANE SKINS SHOP</h2>\n                            <div id=\"shop-items\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); margin-bottom: var(--space-md);\"></div>\n                            <button id=\"close-shop\" style=\"width: 100%; padding: var(--space-sm); background: linear-gradient(135deg, #cc0000 0%, #990000 100%); border: 2px solid rgba(255, 255, 255, 0.4); border-radius: 8px; color: #fff; font-size: 16px; font-weight: 700; cursor: pointer; font-family: 'Orbitron', sans-serif;\">CLOSE</button>\n                        </div>\n                    </div>\n                `;\n\n                setupControls();\n            }\n\n            function updateUI() {\n                const scoreEl = document.getElementById('score-value');\n                const bombEl = document.getElementById('bomb-value');\n                const coinEl = document.getElementById('coin-value');\n                const turretEl = document.getElementById('turret-ammo-value');\n\n                if (scoreEl) scoreEl.textContent = score;\n                if (bombEl) bombEl.textContent = bombsRemaining === -1 ? '\u221e' : bombsRemaining;\n                if (coinEl) coinEl.textContent = coinsCollected;\n                if (turretEl) turretEl.textContent = turretAmmoRemaining === -1 ? '\u221e' : turretAmmoRemaining;\n\n                // Update nuke countdown display\n                const nukeCooldownDisplay = document.getElementById('nuke-cooldown-display');\n                const nukeCountdownValue = document.getElementById('nuke-countdown-value');\n                if (nukeCooldownDisplay && nukeCountdownValue) {\n                    const now = Date.now();\n                    const timeSinceLastNuke = now - lastNukeTime;\n                    const timeRemaining = NUKE_COOLDOWN - timeSinceLastNuke;\n\n                    if (timeRemaining > 0) {\n                        const secondsRemaining = Math.ceil(timeRemaining / 1000);\n                        nukeCountdownValue.textContent = secondsRemaining;\n                        nukeCooldownDisplay.style.display = 'flex';\n                    } else {\n                        nukeCooldownDisplay.style.display = 'none';\n                    }\n                }\n            }\n\n            // ==================== CONTROLS ====================\n            function setupControls() {\n                const joystickZone = document.getElementById('joystick-zone');\n                const joystickBase = document.getElementById('joystick-base');\n                const joystickStick = document.getElementById('joystick-stick');\n                const shopButton = document.getElementById('shop-button');\n                const shopModal = document.getElementById('shop-modal');\n                const closeShopButton = document.getElementById('close-shop');\n                const crosshair = document.getElementById('crosshair');\n                const controlsPanel = document.getElementById('controls-panel');\n                const mobileControls = document.getElementById('mobile-controls');\n                \n                // Mobile buttons\n                const bombButton = document.getElementById('bomb-button');\n                const nukeButton = document.getElementById('nuke-button');\n                const turretButton = document.getElementById('turret-button');\n                const leftButton = document.getElementById('left-button');\n                const rightButton = document.getElementById('right-button');\n                const upButton = document.getElementById('up-button');\n                const downButton = document.getElementById('down-button');\n                const boostButton = document.getElementById('boost-button');\n\n                if (!joystickZone) return;\n\n                // Show/hide controls based on control mode\n                if (config.controlMode === 'mobile') {\n                    if (controlsPanel) controlsPanel.style.display = 'none';\n                    if (mobileControls) mobileControls.style.display = 'flex';\n                } else if (config.controlMode === 'desktop') {\n                    if (controlsPanel) controlsPanel.style.display = 'block';\n                    if (mobileControls) mobileControls.style.display = 'none';\n                }\n\n                // Initialize shop\n                initializeShop();\n                \n                // Mobile button handlers\n                if (bombButton) {\n                    bombButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        dropBomb('normal');\n                    });\n                    bombButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            dropBomb('normal');\n                        }\n                    });\n                }\n                \n                if (nukeButton) {\n                    nukeButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        dropBomb('nuke');\n                    });\n                    nukeButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            dropBomb('nuke');\n                        }\n                    });\n                }\n                \n                if (turretButton) {\n                    turretButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.turretFiring = true;\n                    });\n                    turretButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.turretFiring = false;\n                    });\n                    turretButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.turretFiring = true;\n                        }\n                    });\n                    turretButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.turretFiring = false;\n                        }\n                    });\n                }\n                \n                if (leftButton) {\n                    leftButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.moveX = 1; // Turn left\n                    });\n                    leftButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.moveX = 0;\n                    });\n                    leftButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.moveX = 1;\n                        }\n                    });\n                    leftButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.moveX = 0;\n                        }\n                    });\n                }\n                \n                if (rightButton) {\n                    rightButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.moveX = -1; // Turn right\n                    });\n                    rightButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.moveX = 0;\n                    });\n                    rightButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.moveX = -1;\n                        }\n                    });\n                    rightButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.moveX = 0;\n                        }\n                    });\n                }\n                \n                if (upButton) {\n                    upButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.moveY = 1; // Fly up\n                    });\n                    upButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.moveY = 0;\n                    });\n                    upButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.moveY = 1;\n                        }\n                    });\n                    upButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.moveY = 0;\n                        }\n                    });\n                }\n                \n                if (downButton) {\n                    downButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.moveY = -1; // Fly down\n                    });\n                    downButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.moveY = 0;\n                    });\n                    downButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.moveY = -1;\n                        }\n                    });\n                    downButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.moveY = 0;\n                        }\n                    });\n                }\n                \n                if (boostButton) {\n                    boostButton.addEventListener('touchstart', (e) => {\n                        e.preventDefault();\n                        input.boosting = true;\n                    });\n                    boostButton.addEventListener('touchend', (e) => {\n                        e.preventDefault();\n                        input.boosting = false;\n                    });\n                    boostButton.addEventListener('mousedown', (e) => {\n                        if (e.button === 0) {\n                            e.preventDefault();\n                            input.boosting = true;\n                        }\n                    });\n                    boostButton.addEventListener('mouseup', (e) => {\n                        if (e.button === 0) {\n                            input.boosting = false;\n                        }\n                    });\n                }\n\n                // Add visual feedback for button presses\n                const addButtonFeedback = (button) => {\n                    if (!button) return;\n                    button.addEventListener('touchstart', () => {\n                        button.style.transform = 'scale(0.92)';\n                    });\n                    button.addEventListener('touchend', () => {\n                        button.style.transform = 'scale(1)';\n                    });\n                    button.addEventListener('mousedown', () => {\n                        button.style.transform = 'scale(0.92)';\n                    });\n                    button.addEventListener('mouseup', () => {\n                        button.style.transform = 'scale(1)';\n                    });\n                };\n                \n                addButtonFeedback(bombButton);\n                addButtonFeedback(nukeButton);\n                addButtonFeedback(turretButton);\n                addButtonFeedback(leftButton);\n                addButtonFeedback(rightButton);\n                addButtonFeedback(upButton);\n                addButtonFeedback(downButton);\n                addButtonFeedback(boostButton);\n\n                // Shop button\n                if (shopButton) {\n                    shopButton.addEventListener('click', (e) => {\n                        e.preventDefault();\n                        e.stopPropagation();\n                        if (shopModal) shopModal.style.display = 'flex';\n                    });\n                    addButtonFeedback(shopButton);\n                }\n\n                if (closeShopButton) {\n                    closeShopButton.addEventListener('click', (e) => {\n                        e.preventDefault();\n                        if (shopModal) shopModal.style.display = 'none';\n                    });\n                }\n\n                // Close shop when clicking outside\n                if (shopModal) {\n                    shopModal.addEventListener('click', (e) => {\n                        if (e.target === shopModal) {\n                            shopModal.style.display = 'none';\n                        }\n                    });\n                }\n\n\n                // Touch controls for joystick\n                joystickZone.addEventListener('touchstart', (e) => {\n                    e.preventDefault();\n                    const touch = e.touches[0];\n                    input.joystickActive = true;\n                    input.joystickTouchId = touch.identifier;\n                    input.joystickStartX = touch.clientX;\n                    input.joystickStartY = touch.clientY;\n\n                    joystickBase.style.display = 'block';\n                    joystickBase.style.left = (touch.clientX - 60) + 'px';\n                    joystickBase.style.top = (touch.clientY - 60) + 'px';\n                });\n\n                joystickZone.addEventListener('touchmove', (e) => {\n                    e.preventDefault();\n                    for (let touch of e.touches) {\n                        if (touch.identifier === input.joystickTouchId) {\n                            const dx = touch.clientX - input.joystickStartX;\n                            const dy = touch.clientY - input.joystickStartY;\n                            const maxDist = 50;\n\n                            const dist = Math.min(Math.sqrt(dx * dx + dy * dy), maxDist);\n                            const angle = Math.atan2(dy, dx);\n\n                            const stickX = Math.cos(angle) * dist;\n                            const stickY = Math.sin(angle) * dist;\n\n                            joystickStick.style.left = (60 + stickX) + 'px';\n                            joystickStick.style.top = (60 + stickY) + 'px';\n\n                            input.moveX = stickX / maxDist;\n                            input.moveY = stickY / maxDist;\n                        }\n                    }\n                });\n\n                joystickZone.addEventListener('touchend', (e) => {\n                    for (let touch of e.changedTouches) {\n                        if (touch.identifier === input.joystickTouchId) {\n                            input.joystickActive = false;\n                            input.joystickTouchId = null;\n                            input.moveX = 0;\n                            input.moveY = 0;\n\n                            joystickBase.style.display = 'none';\n                            joystickStick.style.left = '50%';\n                            joystickStick.style.top = '50%';\n                        }\n                    }\n                });\n\n\n                // Keyboard controls\n                const keys = {};\n\n                window.addEventListener('keydown', (e) => {\n                    if (mode !== 'play') return;\n\n                    keys[e.code] = true;\n\n                    if (e.code === 'Space') {\n                        e.preventDefault();\n                        dropBomb('nuke');\n                    }\n\n                    if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') {\n                        input.boosting = true;\n                    }\n\n                    updateKeyboardInput();\n                });\n\n                window.addEventListener('keyup', (e) => {\n                    keys[e.code] = false;\n\n                    if (e.code === 'ShiftLeft' || e.code === 'ShiftRight') {\n                        input.boosting = false;\n                    }\n\n                    updateKeyboardInput();\n                });\n\n                function updateKeyboardInput() {\n                    if (input.joystickActive) return;\n\n                    input.moveX = 0;\n                    input.moveY = 0;\n\n                    if (keys['KeyA']) input.moveX = 1;\n                    if (keys['KeyD']) input.moveX = -1;\n                    if (keys['KeyW'] || keys['ArrowUp']) input.moveY = 1;  // W or Up Arrow = fly up\n                    if (keys['KeyS'] || keys['ArrowDown']) input.moveY = -1;  // S or Down Arrow = fly down\n                }\n\n                // Removed T key for shooting - now using right-click instead\n\n                // Mouse button down/up for bombs\n                canvas.addEventListener('mousedown', (e) => {\n                    if (mode !== 'play') return;\n                    \n                    if (e.button === 0) {\n                        // Left click - drop bomb\n                        dropBomb('normal');\n                    } else if (e.button === 2) {\n                        // Right click - start turret firing\n                        e.preventDefault();\n                        input.turretFiring = true;\n                    }\n                });\n\n                canvas.addEventListener('mouseup', (e) => {\n                    if (e.button === 2) {\n                        input.turretFiring = false;\n                    }\n                });\n\n                // Prevent context menu on right-click\n                canvas.addEventListener('contextmenu', (e) => {\n                    if (mode === 'play') {\n                        e.preventDefault();\n                    }\n                });\n\n                // Mouse look controls\n                let isMouseLocked = false;\n\n                canvas.addEventListener('click', () => {\n                    if (mode === 'play' && canvas.requestPointerLock) {\n                        canvas.requestPointerLock();\n                    }\n                });\n\n                document.addEventListener('pointerlockchange', () => {\n                    isMouseLocked = document.pointerLockElement === canvas;\n                });\n\n                document.addEventListener('mousemove', (e) => {\n                    if (mode !== 'play') return;\n\n                    // Normal camera look\n                    if (isMouseLocked) {\n                        // Use movement delta when pointer is locked\n                        const deltaX = e.movementX || 0;\n                        const deltaY = e.movementY || 0;\n\n                        input.cameraYaw -= deltaX * 0.003;\n                        input.cameraPitch -= deltaY * 0.003;\n\n                        // Clamp pitch to prevent flipping\n                        input.cameraPitch = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, input.cameraPitch));\n                    } else {\n                        // Use relative position when pointer is not locked\n                        const centerX = canvas.clientWidth / 2;\n                        const centerY = canvas.clientHeight / 2;\n\n                        const relX = (e.clientX - centerX) / centerX;\n                        const relY = (e.clientY - centerY) / centerY;\n\n                        input.cameraYaw = relX * Math.PI * 0.3;\n                        input.cameraPitch = relY * Math.PI * 0.2;\n                    }\n                });\n\n                // Resume audio context on interaction\n                document.addEventListener('touchstart', resumeAudio, { once: true });\n                document.addEventListener('click', resumeAudio, { once: true });\n            }\n\n            function resumeAudio() {\n                if (audioContext && audioContext.state === 'suspended') {\n                    audioContext.resume();\n                }\n                startEngineSound();\n            }\n\n            function startEngineSound() {\n                if (mode !== 'play' || engineSound) return;\n                engineSound = playSound('engine_loop', 0.3);\n            }\n\n            // ==================== SHOP SYSTEM ====================\n            const PLANE_SKINS = [\n                { id: 'default', name: 'Steel Grey', color: 0x808080, cost: 0 },\n                { id: 'blue', name: 'Sky Blue', color: 0x0066ff, cost: 700 },\n                { id: 'green', name: 'Forest Green', color: 0x00cc00, cost: 700 },\n                { id: 'gold', name: 'Golden Eagle', color: 0xffaa00, cost: 700 },\n                { id: 'purple', name: 'Violet Storm', color: 0xaa00ff, cost: 700 },\n                { id: 'silver', name: 'Silver Streak', color: 0xcccccc, cost: 700 },\n                { id: 'deepspace', name: 'Deep Space', isDeepSpace: true, cost: 2000 },\n                { id: 'camo', name: 'Tactical Camo', colors: { body: 0x1a4d2e, wings: 0x654321, fins: 0xd4a574 }, cost: 1500 },\n                { id: 'rainbow', name: 'Rainbow Burst', isRainbow: true, cost: 2000 }\n            ];\n\n            function initializeShop() {\n                // Initialize purchased skins in config if not present\n                if (!config.purchasedSkins) {\n                    config.purchasedSkins = ['default'];\n                }\n                if (!config.currentSkin) {\n                    config.currentSkin = 'default';\n                }\n\n                updateShopDisplay();\n            }\n\n            function updateShopDisplay() {\n                const shopItems = document.getElementById('shop-items');\n                if (!shopItems) return;\n\n                shopItems.innerHTML = '';\n\n                PLANE_SKINS.forEach(skin => {\n                    const isPurchased = config.purchasedSkins.includes(skin.id);\n                    const isSelected = config.currentSkin === skin.id;\n\n                    const skinCard = document.createElement('div');\n                    skinCard.style.cssText = `\n                        background: linear-gradient(135deg, rgba(50, 50, 80, 0.8) 0%, rgba(70, 70, 100, 0.8) 100%);\n                        border: 2px solid ${isSelected ? '#ffd700' : 'rgba(100, 180, 255, 0.3)'};\n                        border-radius: 12px;\n                        padding: var(--space-md);\n                        text-align: center;\n                        cursor: ${isPurchased ? 'pointer' : 'not-allowed'};\n                        opacity: ${isPurchased ? 1 : 0.6};\n                        transition: all var(--transition-normal) var(--ease-out);\n                    `;\n\n                    const colorPreview = document.createElement('div');\n                    \n                    // Determine preview color\n                    let previewColor;\n                    if (skin.colors) {\n                        // For multi-color skins, show the body color\n                        previewColor = `rgb(${(skin.colors.body >> 16) & 255}, ${(skin.colors.body >> 8) & 255}, ${skin.colors.body & 255})`;\n                    } else if (skin.isRainbow) {\n                        // For rainbow, show a gradient\n                        previewColor = 'linear-gradient(90deg, #ff0000, #ffff00, #00ff00, #00ffff, #0000ff, #ff00ff)';\n                    } else if (skin.isDeepSpace) {\n                        // For Deep Space, show a starfield-like preview\n                        previewColor = 'radial-gradient(circle at 20% 30%, #ffffff 1px, transparent 1px), radial-gradient(circle at 60% 70%, #ffffff 0.5px, transparent 0.5px), radial-gradient(circle at 80% 10%, #ffffff 1.5px, transparent 1.5px), linear-gradient(135deg, #0a0e27 0%, #1a1a3e 100%)';\n                    } else {\n                        // For single color skins\n                        previewColor = `rgb(${(skin.color >> 16) & 255}, ${(skin.color >> 8) & 255}, ${skin.color & 255})`;\n                    }\n                    \n                    colorPreview.style.cssText = `\n                        width: 60px;\n                        height: 60px;\n                        background: ${previewColor};\n                        border-radius: 8px;\n                        margin: 0 auto var(--space-sm);\n                        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);\n                    `;\n\n                    const nameEl = document.createElement('div');\n                    nameEl.style.cssText = 'font-weight: 700; margin-bottom: 4px; color: #fff;';\n                    nameEl.textContent = skin.name;\n\n                    const statusEl = document.createElement('div');\n                    statusEl.style.cssText = 'font-size: 12px; margin-bottom: 8px;';\n                    if (isSelected) {\n                        statusEl.textContent = '\u2713 EQUIPPED';\n                        statusEl.style.color = '#ffd700';\n                    } else if (isPurchased) {\n                        statusEl.textContent = 'OWNED';\n                        statusEl.style.color = '#90ee90';\n                    } else {\n                        statusEl.textContent = `${skin.cost} \ud83e\ude99`;\n                        statusEl.style.color = '#ffaa00';\n                    }\n\n                    skinCard.appendChild(colorPreview);\n                    skinCard.appendChild(nameEl);\n                    skinCard.appendChild(statusEl);\n\n                    if (isPurchased) {\n                        skinCard.addEventListener('click', () => {\n                            selectSkin(skin.id);\n                        });\n                        skinCard.addEventListener('mouseover', () => {\n                            skinCard.style.transform = 'scale(1.05)';\n                        });\n                        skinCard.addEventListener('mouseout', () => {\n                            skinCard.style.transform = 'scale(1)';\n                        });\n                    } else {\n                        const buyBtn = document.createElement('button');\n                        buyBtn.textContent = 'BUY';\n                        buyBtn.style.cssText = `\n                            width: 100%;\n                            padding: 6px;\n                            background: linear-gradient(135deg, #0066ff 0%, #0044cc 100%);\n                            border: 1px solid rgba(255, 255, 255, 0.3);\n                            border-radius: 6px;\n                            color: #fff;\n                            font-weight: 700;\n                            cursor: pointer;\n                            font-family: 'Orbitron', sans-serif;\n                            font-size: 12px;\n                        `;\n                        buyBtn.addEventListener('click', () => {\n                            buySkin(skin.id, skin.cost);\n                        });\n                        skinCard.appendChild(buyBtn);\n                    }\n\n                    shopItems.appendChild(skinCard);\n                });\n            }\n\n            function selectSkin(skinId) {\n                config.currentSkin = skinId;\n                const skin = PLANE_SKINS.find(s => s.id === skinId);\n                if (skin && planeMesh) {\n                    if (skin.colors) {\n                        // Multi-color skin (like camo)\n                        const fuselage = planeMesh.getObjectByName('fuselage');\n                        const wings = planeMesh.getObjectByName('wings');\n                        const tail = planeMesh.getObjectByName('tail');\n                        const stabilizer = planeMesh.getObjectByName('stabilizer');\n                        \n                        if (fuselage && fuselage.material) fuselage.material.color.setHex(skin.colors.body);\n                        if (wings && wings.material) wings.material.color.setHex(skin.colors.wings);\n                        if (tail && tail.material) tail.material.color.setHex(skin.colors.body);\n                        if (stabilizer && stabilizer.material) stabilizer.material.color.setHex(skin.colors.body);\n                    } else if (skin.isRainbow) {\n                        // Rainbow skin - colors will be updated in game loop\n                        rainbowColorTime = 0;\n                    } else if (skin.isDeepSpace) {\n                        // Deep Space skin - apply starfield texture to entire plane\n                        const starTexture = createStarfieldTexture();\n                        const fuselage = planeMesh.getObjectByName('fuselage');\n                        const wings = planeMesh.getObjectByName('wings');\n                        const tail = planeMesh.getObjectByName('tail');\n                        const stabilizer = planeMesh.getObjectByName('stabilizer');\n                        \n                        if (fuselage && fuselage.material) {\n                            fuselage.material.map = starTexture;\n                            fuselage.material.color.setHex(0x0a0e27);\n                        }\n                        if (wings && wings.material) {\n                            wings.material.map = starTexture;\n                            wings.material.color.setHex(0x0f1535);\n                        }\n                        if (tail && tail.material) {\n                            tail.material.map = starTexture;\n                            tail.material.color.setHex(0x0a0e27);\n                        }\n                        if (stabilizer && stabilizer.material) {\n                            stabilizer.material.map = starTexture;\n                            stabilizer.material.color.setHex(0x0a0e27);\n                        }\n                        \n                        // Also update cockpit and nose\n                        planeMesh.children.forEach(child => {\n                            if ((child.name === 'cockpit' || child.name === 'nose') && child.material) {\n                                child.material.map = starTexture;\n                                child.material.color.setHex(0x0a0e27);\n                            }\n                        });\n                    } else {\n                        // Single color skin\n                        planeMesh.children.forEach(child => {\n                            if (child.material && child.material.color && child.name !== 'cockpit' && child.name !== 'nose') {\n                                child.material.color.setHex(skin.color);\n                                if (child.material.map) child.material.map = null;\n                            }\n                        });\n                    }\n                }\n                updateShopDisplay();\n                saveGameState();\n            }\n\n            function buySkin(skinId, cost) {\n                if (coinsCollected >= cost) {\n                    coinsCollected -= cost;\n                    config.purchasedSkins.push(skinId);\n                    selectSkin(skinId);\n                    updateShopDisplay();\n                    saveGameState();\n                } else {\n                    // Show insufficient coins message\n                    const shopModal = document.getElementById('shop-modal');\n                    if (shopModal) {\n                        const msg = document.createElement('div');\n                        msg.textContent = `Need ${cost} coins, have ${coinsCollected}`;\n                        msg.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(255, 0, 0, 0.8); color: #fff; padding: 20px; border-radius: 8px; z-index: 2000; font-weight: 700;';\n                        document.body.appendChild(msg);\n                        setTimeout(() => msg.remove(), 2000);\n                    }\n                }\n            }\n\n            function updateRainbowPlane(deltaTime) {\n                if (config.currentSkin !== 'rainbow' || !planeMesh) return;\n\n                rainbowColorTime += deltaTime;\n                \n                // Cycle through rainbow colors every 0.5 seconds\n                const cycleTime = 0.5;\n                const progress = (rainbowColorTime % cycleTime) / cycleTime;\n                \n                // Rainbow color sequence: red -> yellow -> green -> cyan -> blue -> magenta -> red\n                const rainbowColors = [\n                    0xff0000, // red\n                    0xffff00, // yellow\n                    0x00ff00, // green\n                    0x00ffff, // cyan\n                    0x0000ff, // blue\n                    0xff00ff  // magenta\n                ];\n                \n                const colorIndex = Math.floor(progress * rainbowColors.length);\n                const nextColorIndex = (colorIndex + 1) % rainbowColors.length;\n                const colorProgress = (progress * rainbowColors.length) % 1;\n                \n                // Interpolate between current and next color\n                const currentColor = rainbowColors[colorIndex];\n                const nextColor = rainbowColors[nextColorIndex];\n                \n                const r1 = (currentColor >> 16) & 255;\n                const g1 = (currentColor >> 8) & 255;\n                const b1 = currentColor & 255;\n                \n                const r2 = (nextColor >> 16) & 255;\n                const g2 = (nextColor >> 8) & 255;\n                const b2 = nextColor & 255;\n                \n                const r = Math.round(r1 + (r2 - r1) * colorProgress);\n                const g = Math.round(g1 + (g2 - g1) * colorProgress);\n                const b = Math.round(b1 + (b2 - b1) * colorProgress);\n                \n                const interpolatedColor = (r << 16) | (g << 8) | b;\n                \n                // Update all plane parts with the rainbow color\n                planeMesh.children.forEach(child => {\n                    if (child.material && child.material.color && child.name !== 'cockpit' && child.name !== 'nose') {\n                        child.material.color.setHex(interpolatedColor);\n                    }\n                });\n            }\n\n            // ==================== SAVE/LOAD SYSTEM ====================\n            async function saveGameState() {\n                if (mode !== 'play') return;\n                \n                try {\n                    const saveData = {\n                        coins: coinsCollected,\n                        score: score,\n                        purchasedSkins: config.purchasedSkins,\n                        currentSkin: config.currentSkin\n                    };\n                    await lib.saveUserGameState(saveData);\n                    lib.log('Progress saved successfully');\n                } catch (error) {\n                    lib.log(`Failed to save progress: ${error.message}`);\n                }\n            }\n\n            async function loadGameState() {\n                if (mode !== 'play') return;\n                \n                try {\n                    const response = await lib.getUserGameState();\n                    if (response.state) {\n                        // Restore saved data\n                        coinsCollected = response.state.coins || 0;\n                        score = response.state.score || 0;\n                        config.purchasedSkins = response.state.purchasedSkins || ['default'];\n                        config.currentSkin = response.state.currentSkin || 'default';\n                        lib.log('Progress loaded successfully');\n                    } else {\n                        // New player - use defaults\n                        lib.log('No saved progress found, starting fresh');\n                    }\n                } catch (error) {\n                    lib.log(`Failed to load progress: ${error.message}`);\n                }\n            }\n\n            function findLockedOnPlane() {\n                // Find the closest AI plane in front of the player\n                let closestPlane = null;\n                let closestDistance = Infinity;\n                const lockOnDistance = 400; // Increased lock-on distance\n                const lockOnAngle = Math.PI / 12; // 15 degree cone - very narrow, only directly in front\n\n                for (const ai of aiPlanes) {\n                    // Vector from player to AI plane\n                    const dx = ai.x - player.x;\n                    const dz = ai.z - player.z;\n                    const dy = ai.y - player.y;\n                    const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);\n\n                    // Check if within lock-on distance\n                    if (distance > lockOnDistance) continue;\n\n                    // Check if in front of player (within cone)\n                    const planeForwardX = -Math.sin(player.rotation);\n                    const planeForwardZ = -Math.cos(player.rotation);\n                    \n                    // Normalize the vector to AI plane\n                    const toAIX = dx / distance;\n                    const toAIZ = dz / distance;\n                    \n                    // Calculate dot product (cosine of angle)\n                    const dotProduct = toAIX * planeForwardX + toAIZ * planeForwardZ;\n                    \n                    // Only lock on if AI is in front (positive dot product) and within cone\n                    if (dotProduct > Math.cos(lockOnAngle) && distance < closestDistance) {\n                        closestPlane = ai;\n                        closestDistance = distance;\n                    }\n                }\n\n                return closestPlane;\n            }\n\n            function shootBullet() {\n                if (mode !== 'play') return;\n\n                // Check turret ammo\n                if (turretAmmoRemaining === 0) return;\n                if (turretAmmoRemaining > 0) turretAmmoRemaining--;\n\n                // Use already-locked plane (updated every frame in updateLockOn)\n                // Create bullet mesh\n                const bulletGeometry = new THREE.SphereGeometry(0.5, 4, 4);\n                const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });\n                const bulletMesh = new THREE.Mesh(bulletGeometry, bulletMaterial);\n                \n                // Spawn bullet in front of player\n                const spawnDistance = 20;\n                const spawnX = player.x - Math.sin(player.rotation) * spawnDistance;\n                const spawnY = player.y + 5; // Above player\n                const spawnZ = player.z - Math.cos(player.rotation) * spawnDistance;\n                \n                bulletMesh.position.set(spawnX, spawnY, spawnZ);\n                scene.add(bulletMesh);\n\n                // Determine direction with predictive aiming\n                let dirX, dirY, dirZ;\n                if (lockedOnPlane) {\n                    // Predict where the target will be\n                    // Estimate time to intercept based on bullet speed and distance\n                    const bulletSpeed = 500; // Increased from 300\n                    const targetDx = lockedOnPlane.x - spawnX;\n                    const targetDy = lockedOnPlane.y - spawnY;\n                    const targetDz = lockedOnPlane.z - spawnZ;\n                    const distToTarget = Math.sqrt(targetDx * targetDx + targetDy * targetDy + targetDz * targetDz);\n                    \n                    // Estimate intercept time\n                    const interceptTime = distToTarget / bulletSpeed;\n                    \n                    // Predict target position at intercept time\n                    // Target velocity (approximate from plane speed and direction)\n                    const targetVelX = -Math.sin(lockedOnPlane.rotation) * lockedOnPlane.speed;\n                    const targetVelZ = -Math.cos(lockedOnPlane.rotation) * lockedOnPlane.speed;\n                    const targetVelY = 0; // Assume level flight\n                    \n                    // Predicted position\n                    const predictedX = lockedOnPlane.x + targetVelX * interceptTime;\n                    const predictedY = lockedOnPlane.y + targetVelY * interceptTime;\n                    const predictedZ = lockedOnPlane.z + targetVelZ * interceptTime;\n                    \n                    // Aim at predicted position\n                    dirX = predictedX - spawnX;\n                    dirY = predictedY - spawnY;\n                    dirZ = predictedZ - spawnZ;\n                } else {\n                    // Shoot forward and slightly up\n                    dirX = -Math.sin(player.rotation);\n                    dirY = 0.2; // Slight upward angle\n                    dirZ = -Math.cos(player.rotation);\n                }\n\n                // Normalize direction\n                const dirLength = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);\n                dirX /= dirLength;\n                dirY /= dirLength;\n                dirZ /= dirLength;\n\n                bullets.push({\n                    mesh: bulletMesh,\n                    x: spawnX,\n                    y: spawnY,\n                    z: spawnZ,\n                    velocityX: dirX * 500,\n                    velocityY: dirY * 500,\n                    velocityZ: dirZ * 500,\n                    life: 5.0, // 5 seconds max lifetime\n                    targetPlane: lockedOnPlane // Store reference to locked-on plane for homing\n                });\n            }\n\n            function updateCoins(deltaTime) {\n                // Check for coin collection\n                for (let i = coins.length - 1; i >= 0; i--) {\n                    const coin = coins[i];\n\n                    // Rotate coin\n                    coin.rotation += deltaTime * 3;\n                    coin.mesh.rotation.y = coin.rotation;\n\n                    // Bob up and down\n                    coin.mesh.position.y = 5 + Math.sin(coin.rotation) * 2;\n\n                    // Check distance to player\n                    const dx = player.x - coin.x;\n                    const dz = player.z - coin.z;\n                    const distance = Math.sqrt(dx * dx + dz * dz);\n\n                    if (distance < 30) {\n                        // Collect coin\n                        coinsCollected++;\n                        scene.remove(coin.mesh);\n                        coins.splice(i, 1);\n                        saveGameState();\n                    }\n                }\n            }\n\n            // ==================== GAME LOGIC ====================\n\n\n            function updateBullets(deltaTime) {\n                // Continuous turret firing when held down\n                if (mode === 'play' && input.turretFiring) {\n                    const now = Date.now();\n                    const fireRate = 100; // milliseconds between shots\n                    if (now - input.lastTurretFireTime >= fireRate) {\n                        shootBullet();\n                        input.lastTurretFireTime = now;\n                    }\n                }\n\n                for (let i = bullets.length - 1; i >= 0; i--) {\n                    const bullet = bullets[i];\n                    \n                    bullet.life -= deltaTime;\n                    \n                    // Home in on locked-on plane if available\n                    if (bullet.targetPlane && bullet.targetPlane.health > 0) {\n                        // Calculate direction to target\n                        const dx = bullet.targetPlane.x - bullet.x;\n                        const dy = bullet.targetPlane.y - bullet.y;\n                        const dz = bullet.targetPlane.z - bullet.z;\n                        const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);\n                        \n                        if (distance > 0) {\n                            // Normalize direction\n                            const dirX = dx / distance;\n                            const dirY = dy / distance;\n                            const dirZ = dz / distance;\n                            \n                            // Very aggressive homing - strongly curve toward target\n                            const homingStrength = 1.2; // Very strong homing for better tracking\n                            const currentSpeed = Math.sqrt(bullet.velocityX ** 2 + bullet.velocityY ** 2 + bullet.velocityZ ** 2);\n                            \n                            bullet.velocityX += dirX * currentSpeed * homingStrength * deltaTime;\n                            bullet.velocityY += dirY * currentSpeed * homingStrength * deltaTime;\n                            bullet.velocityZ += dirZ * currentSpeed * homingStrength * deltaTime;\n                            \n                            // Normalize velocity to maintain speed\n                            const newSpeed = Math.sqrt(bullet.velocityX ** 2 + bullet.velocityY ** 2 + bullet.velocityZ ** 2);\n                            if (newSpeed > 0) {\n                                const speedFactor = currentSpeed / newSpeed;\n                                bullet.velocityX *= speedFactor;\n                                bullet.velocityY *= speedFactor;\n                                bullet.velocityZ *= speedFactor;\n                            }\n                        }\n                    }\n                    \n                    // Update position\n                    bullet.x += bullet.velocityX * deltaTime;\n                    bullet.y += bullet.velocityY * deltaTime;\n                    bullet.z += bullet.velocityZ * deltaTime;\n                    \n                    bullet.mesh.position.set(bullet.x, bullet.y, bullet.z);\n                    \n                    // Check collision with AI planes\n                    let hit = false;\n                    for (let j = aiPlanes.length - 1; j >= 0; j--) {\n                        const ai = aiPlanes[j];\n                        \n                        const dx = bullet.x - ai.x;\n                        const dy = bullet.y - ai.y;\n                        const dz = bullet.z - ai.z;\n                        const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);\n                        \n                        // Hit radius for AI plane\n                        if (distance < 15) {\n                            hit = true;\n                            ai.health -= 1;\n                            \n                            if (ai.health <= 0) {\n                                // Destroy AI plane\n                                scene.remove(ai.mesh);\n                                if (ai.healthBarContainer) {\n                                    ai.healthBarContainer.remove();\n                                }\n                                aiPlanes.splice(j, 1);\n                                score += 100;\n                                coinsCollected += 200;\n                                saveGameState();\n                                \n                                // Create explosion at AI plane location\n                                const explosion = createExplosion(ai.x, ai.y, ai.z, 'normal');\n                                explosions.push(explosion);\n                                playSound('explosion_sound', 0.6);\n                            }\n                            break;\n                        }\n                    }\n                    \n                    // Remove bullet if hit or too old\n                    if (hit || bullet.life <= 0) {\n                        scene.remove(bullet.mesh);\n                        bullets.splice(i, 1);\n                    }\n                }\n            }\n\n            function dropBomb(bombType = 'normal') {\n                if (mode !== 'play') return;\n\n                const now = Date.now();\n\n                if (bombType === 'normal') {\n                    // Normal bomb - no cooldown\n                    if (bombsRemaining === 0) return;\n                    if (bombsRemaining > 0) bombsRemaining--;\n                } else if (bombType === 'nuke') {\n                    // Nuke - 5 second cooldown\n                    if (now - lastNukeTime < NUKE_COOLDOWN) return;\n                    lastNukeTime = now;\n                } else {\n                    return;\n                }\n\n                // Create bomb at plane position\n                const bombMesh = createBomb();\n                bombMesh.position.set(player.x, player.y - 5, player.z);\n                bombMesh.rotation.copy(planeMesh.rotation);\n                scene.add(bombMesh);\n\n                // Calculate forward direction based on plane rotation\n                const forwardX = -Math.sin(player.rotation) * config.planeSpeed * 0.5;\n                const forwardZ = -Math.cos(player.rotation) * config.planeSpeed * 0.5;\n\n                bombs.push({\n                    mesh: bombMesh,\n                    x: player.x,\n                    y: player.y - 5,\n                    z: player.z,\n                    velocityX: forwardX,\n                    velocityY: 0,\n                    velocityZ: forwardZ,\n                    type: bombType\n                });\n\n                playSound('bomb_drop_sound', 0.5);\n                updateUI();\n\n                // Visual feedback on button\n                if (bombType === 'normal') {\n                    const bombButton = document.getElementById('bomb-button');\n                    if (bombButton) {\n                        bombButton.classList.add('cooldown');\n                        setTimeout(() => bombButton.classList.remove('cooldown'), 100);\n                    }\n                } else if (bombType === 'nuke') {\n                    const nukeButton = document.getElementById('nuke-button');\n                    if (nukeButton) {\n                        nukeButton.classList.add('cooldown');\n                        setTimeout(() => nukeButton.classList.remove('cooldown'), NUKE_COOLDOWN);\n                    }\n                }\n            }\n\n            function destroyBuildingsInRadius(explosionX, explosionY, explosionZ, explosionRadius) {\n                // Destroy all buildings within explosion radius\n                for (const building of buildings) {\n                    if (building.destroyed) continue;\n\n                    const dx = explosionX - building.x;\n                    const dz = explosionZ - building.z;\n                    const distance = Math.sqrt(dx * dx + dz * dz);\n\n                    if (distance < explosionRadius + Math.max(building.width, building.depth) / 2) {\n                        building.destroyed = true;\n                        scene.remove(building.mesh);\n                        score += Math.floor(building.height / 10) * config.scoreMultiplier;\n                        \n                        // Award coins for destroying building\n                        coinsCollected += 1;\n                        saveGameState();\n                    }\n                }\n            }\n\n            function updateBombs(deltaTime) {\n                for (let i = bombs.length - 1; i >= 0; i--) {\n                    const bomb = bombs[i];\n\n                    // Apply gravity\n                    bomb.velocityY -= config.bombGravity * deltaTime;\n\n                    // Update position\n                    bomb.x += bomb.velocityX * deltaTime;\n                    bomb.y += bomb.velocityY * deltaTime;\n                    bomb.z += bomb.velocityZ * deltaTime;\n\n                    bomb.mesh.position.set(bomb.x, bomb.y, bomb.z);\n\n                    // Rotate bomb to face direction of travel\n                    const speed = Math.sqrt(bomb.velocityX ** 2 + bomb.velocityY ** 2 + bomb.velocityZ ** 2);\n                    if (speed > 0) {\n                        bomb.mesh.rotation.x = Math.atan2(-bomb.velocityY, Math.sqrt(bomb.velocityX ** 2 + bomb.velocityZ ** 2));\n                    }\n\n                    // Determine explosion radius based on bomb type\n                    const explosionRadius = bomb.type === 'nuke' ? 350 : config.explosionRadius;\n\n                    // Check collision with buildings\n                    let hit = false;\n                    for (const building of buildings) {\n                        if (building.destroyed) continue;\n\n                        const dx = Math.abs(bomb.x - building.x);\n                        const dz = Math.abs(bomb.z - building.z);\n                        const dy = bomb.y - building.height;\n\n                        if (dx < building.width / 2 + explosionRadius &&\n                            dz < building.depth / 2 + explosionRadius &&\n                            bomb.y < building.height && bomb.y > 0) {\n\n                            // Hit!\n                            hit = true;\n\n                            // Create explosion\n                            const explosion = createExplosion(bomb.x, bomb.y, bomb.z, bomb.type);\n                            explosions.push(explosion);\n\n                            // Destroy all buildings in explosion radius\n                            destroyBuildingsInRadius(bomb.x, bomb.y, bomb.z, explosionRadius);\n\n                            playSound('explosion_sound', 0.6);\n                            break;\n                        }\n                    }\n\n                    // Check ground collision\n                    if (bomb.y <= 0) {\n                        hit = true;\n                        const explosion = createExplosion(bomb.x, 5, bomb.z, bomb.type);\n                        explosions.push(explosion);\n\n                        // Destroy buildings in explosion radius\n                        destroyBuildingsInRadius(bomb.x, 5, bomb.z, explosionRadius);\n\n                        playSound('explosion_sound', 0.4);\n                    }\n\n                    // Remove bomb if hit or too far\n                    if (hit || bomb.y < -50 || Math.abs(bomb.z - player.z) > 500) {\n                        scene.remove(bomb.mesh);\n                        bombs.splice(i, 1);\n                    }\n                }\n            }\n\n            function updateExplosions(deltaTime) {\n                for (let i = explosions.length - 1; i >= 0; i--) {\n                    const explosion = explosions[i];\n\n                    // Slower fade for nukes (0.4x speed), faster for normal bombs (2x speed)\n                    const fadeRate = explosion.type === 'nuke' ? deltaTime * 0.4 : deltaTime * 2;\n                    explosion.life -= fadeRate;\n\n                    // Scale up and fade out\n                    const scale = 1 + (1 - explosion.life) * 2;\n                    explosion.mesh.scale.setScalar(scale);\n\n                    explosion.mesh.children.forEach(child => {\n                        if (child.material) {\n                            child.material.opacity = explosion.life * 0.8;\n                        }\n                    });\n\n                    if (explosion.life <= 0) {\n                        scene.remove(explosion.mesh);\n                        explosions.splice(i, 1);\n                    }\n                }\n            }\n\n            function createJetStreamParticle() {\n                // Create a small glowing sphere for jet stream\n                const geometry = new THREE.SphereGeometry(1, 4, 4);\n                const material = new THREE.MeshBasicMaterial({\n                    color: 0xff8800,\n                    transparent: true,\n                    opacity: 0.8\n                });\n                const particle = new THREE.Mesh(geometry, material);\n                scene.add(particle);\n\n                // Random offset behind plane\n                const offsetX = (Math.random() - 0.5) * 3;\n                const offsetY = (Math.random() - 0.5) * 2;\n\n                return {\n                    mesh: particle,\n                    x: player.x + Math.sin(player.rotation) * 8 + offsetX,\n                    y: player.y - 2 + offsetY,\n                    z: player.z + Math.cos(player.rotation) * 8,\n                    velocityX: (Math.random() - 0.5) * 20,\n                    velocityY: (Math.random() - 0.5) * 20,\n                    velocityZ: (Math.random() - 0.5) * 20,\n                    life: 0.5\n                };\n            }\n\n            function updateJetStream(deltaTime) {\n                // Spawn new particles when boosting\n                if (input.boosting && mode === 'play') {\n                    for (let i = 0; i < 3; i++) {\n                        jetStreamParticles.push(createJetStreamParticle());\n                    }\n                }\n\n                // Update existing particles\n                for (let i = jetStreamParticles.length - 1; i >= 0; i--) {\n                    const particle = jetStreamParticles[i];\n\n                    particle.life -= deltaTime * 2;\n\n                    // Apply some drag\n                    particle.velocityX *= 0.95;\n                    particle.velocityY *= 0.95;\n                    particle.velocityZ *= 0.95;\n\n                    // Update position\n                    particle.x += particle.velocityX * deltaTime;\n                    particle.y += particle.velocityY * deltaTime;\n                    particle.z += particle.velocityZ * deltaTime;\n\n                    particle.mesh.position.set(particle.x, particle.y, particle.z);\n\n                    // Fade out\n                    particle.mesh.scale.setScalar(1 - (0.5 - particle.life) / 0.5);\n                    if (particle.mesh.material) {\n                        particle.mesh.material.opacity = particle.life * 0.8;\n                    }\n\n                    // Remove dead particles\n                    if (particle.life <= 0) {\n                        scene.remove(particle.mesh);\n                        jetStreamParticles.splice(i, 1);\n                    }\n                }\n            }\n\n            function updateLockOn() {\n                // Continuously update lock-on to closest enemy in range\n                lockedOnPlane = findLockedOnPlane();\n            }\n\n            function updatePlayer(deltaTime) {\n                // Normal flight mode\n                // Turn based on input (yaw - only from horizontal input)\n                const turnAmount = input.moveX * config.turnSpeed * deltaTime;\n                player.rotation += turnAmount;\n\n                // Bank (roll) based on turning - only when turning horizontally\n                // Roll should ONLY be affected by horizontal input (moveX), not vertical\n                const targetRoll = input.moveX * 0.5;\n                player.roll += (targetRoll - player.roll) * 12 * deltaTime;\n\n                // NO pitch rotation - plane stays level when moving up/down\n                // Just apply altitude changes without rotating the plane\n\n                // When NO input is being pressed, smoothly return to completely flat orientation\n                if (Math.abs(input.moveX) < 0.01 && Math.abs(input.moveY) < 0.01) {\n                    // Correct roll back to 0 (strong correction)\n                    player.roll += (0 - player.roll) * 15 * deltaTime;\n                    // Correct pitch back to 0 (strong correction)\n                    player.pitch += (0 - player.pitch) * 15 * deltaTime;\n                    // Keep yaw (rotation) as-is - don't spin to face a direction\n                    \n                    // Snap to zero if very close to prevent floating point drift\n                    if (Math.abs(player.roll) < 0.001) player.roll = 0;\n                    if (Math.abs(player.pitch) < 0.001) player.pitch = 0;\n                }\n\n                // Move forward with boost multiplier\n                let speed = config.planeSpeed;\n                if (input.boosting) {\n                    speed *= config.boostMultiplier;\n                }\n                player.x -= Math.sin(player.rotation) * speed * deltaTime;\n                player.z -= Math.cos(player.rotation) * speed * deltaTime;\n\n                // Direct altitude control with W/S keys\n                // moveY: positive = up (W), negative = down (S)\n                const altitudeSpeed = 100; // units per second\n                player.y += input.moveY * altitudeSpeed * deltaTime;\n\n                // Find max building height in loaded area\n                let maxBuildingHeight = 50; // Default minimum\n                for (const building of buildings) {\n                    if (building.height > maxBuildingHeight) {\n                        maxBuildingHeight = building.height;\n                    }\n                }\n\n                // Clamp altitude: min = just above max building, max = 5x max building height\n                const minAltitude = maxBuildingHeight + 10;\n                const maxAltitude = maxBuildingHeight * 5;\n                player.y = Math.max(minAltitude, Math.min(maxAltitude, player.y));\n\n                // Update plane mesh\n                planeMesh.position.set(player.x, player.y, player.z);\n                planeMesh.rotation.set(player.pitch, player.rotation, player.roll);\n            }\n\n            function updateCamera() {\n                // Chase camera behind and above plane, with mouse look control\n                let cameraDistance = config.cameraDistance || 120;\n                let cameraHeight = (config.cameraDistance || 120) * 0.5;\n\n                // Apply mouse look adjustments\n                const yawOffset = input.cameraYaw;\n                const pitchOffset = input.cameraPitch;\n\n                // Adjust distance and height based on pitch\n                cameraDistance += pitchOffset * 30;\n                cameraHeight += pitchOffset * 15;\n\n                // Calculate camera position with yaw offset\n                const baseAngle = player.rotation + yawOffset;\n                const cameraX = player.x + Math.sin(baseAngle) * cameraDistance;\n                const cameraZ = player.z + Math.cos(baseAngle) * cameraDistance;\n                const cameraY = player.y + cameraHeight;\n\n                camera.position.set(cameraX, cameraY, cameraZ);\n                camera.lookAt(player.x, player.y, player.z);\n\n                // Update crosshair position to be in front and above player\n                updateCrosshairPosition();\n            }\n\n            function updateCrosshairPosition() {\n                const crosshair = document.getElementById('crosshair');\n                if (!crosshair) return;\n\n                let targetWorldX, targetWorldY, targetWorldZ;\n\n                // If locked on to a plane, follow it; otherwise show forward position\n                if (lockedOnPlane && lockedOnPlane.health > 0) {\n                    targetWorldX = lockedOnPlane.x;\n                    targetWorldY = lockedOnPlane.y;\n                    targetWorldZ = lockedOnPlane.z;\n                } else {\n                    // Default: in front and above player\n                    const forwardDistance = 100;\n                    const upOffset = 30;\n                    \n                    targetWorldX = player.x - Math.sin(player.rotation) * forwardDistance;\n                    targetWorldY = player.y + upOffset;\n                    targetWorldZ = player.z - Math.cos(player.rotation) * forwardDistance;\n                }\n\n                // Project to screen space\n                const vector = new THREE.Vector3(targetWorldX, targetWorldY, targetWorldZ);\n                vector.project(camera);\n\n                // Convert to screen coordinates\n                const canvas = document.getElementById('game-canvas');\n                const screenX = (vector.x * 0.5 + 0.5) * canvas.clientWidth;\n                const screenY = (-vector.y * 0.5 + 0.5) * canvas.clientHeight;\n\n                // Position crosshair\n                crosshair.style.left = screenX + 'px';\n                crosshair.style.top = screenY + 'px';\n                crosshair.style.transform = 'translate(-50%, -50%)';\n            }\n\n            function updateCity() {\n                // Get current chunk coordinates\n                const playerChunkX = Math.floor(player.x / CHUNK_SIZE);\n                const playerChunkZ = Math.floor(player.z / CHUNK_SIZE);\n\n                // Track which chunks are loaded\n                const loadedChunks = new Set();\n                buildings.forEach(b => {\n                    const chunkX = Math.floor(b.x / CHUNK_SIZE);\n                    const chunkZ = Math.floor(b.z / CHUNK_SIZE);\n                    loadedChunks.add(`${chunkX},${chunkZ}`);\n                });\n\n                // Generate chunks in a square around the player\n                for (let dx = -CHUNK_LOAD_DISTANCE; dx <= CHUNK_LOAD_DISTANCE; dx++) {\n                    for (let dz = -CHUNK_LOAD_DISTANCE; dz <= CHUNK_LOAD_DISTANCE; dz++) {\n                        const chunkX = playerChunkX + dx;\n                        const chunkZ = playerChunkZ + dz;\n                        const chunkKey = `${chunkX},${chunkZ}`;\n\n                        if (!loadedChunks.has(chunkKey)) {\n                            generateCityChunk(chunkX, chunkZ);\n                        }\n                    }\n                }\n\n                // Remove buildings far away\n                for (let i = buildings.length - 1; i >= 0; i--) {\n                    const building = buildings[i];\n                    const chunkX = Math.floor(building.x / CHUNK_SIZE);\n                    const chunkZ = Math.floor(building.z / CHUNK_SIZE);\n                    const distX = Math.abs(chunkX - playerChunkX);\n                    const distZ = Math.abs(chunkZ - playerChunkZ);\n\n                    if (distX > CHUNK_UNLOAD_DISTANCE || distZ > CHUNK_UNLOAD_DISTANCE) {\n                        scene.remove(building.mesh);\n                        buildings.splice(i, 1);\n                    }\n                }\n\n                // Update ground position to follow player\n                if (groundMesh) {\n                    groundMesh.position.x = player.x;\n                    groundMesh.position.z = player.z;\n                }\n            }\n\n            // ==================== INITIALIZATION ====================\n            async function initGame() {\n                // Load saved game state first (only in play mode)\n                if (mode === 'play') {\n                    await loadGameState();\n                }\n\n                createGround();\n                createPlane();\n\n                // Generate initial city chunks around starting position\n                for (let dx = -CHUNK_LOAD_DISTANCE; dx <= CHUNK_LOAD_DISTANCE; dx++) {\n                    for (let dz = -CHUNK_LOAD_DISTANCE; dz <= CHUNK_LOAD_DISTANCE; dz++) {\n                        generateCityChunk(dx, dz);\n                    }\n                }\n\n                // Position player\n                player.x = 0;\n                player.y = PLANE_ALTITUDE;\n                player.z = 0;\n                player.rotation = 0;\n\n                updateCamera();\n            }\n\n            // ==================== GAME PARAMETERS ====================\n            lib.showGameParameters({\n                name: 'Sky Strike Settings',\n                params: {\n                    'Plane Speed': {\n                        key: 'planeSpeed',\n                        type: 'slider',\n                        min: 50,\n                        max: 200,\n                        step: 10\n                    },\n                    'Boost Multiplier': {\n                        key: 'boostMultiplier',\n                        type: 'slider',\n                        min: 1.5,\n                        max: 3.0,\n                        step: 0.1\n                    },\n                    'Turn Speed': {\n                        key: 'turnSpeed',\n                        type: 'slider',\n                        min: 0.5,\n                        max: 3.0,\n                        step: 0.1\n                    },\n                    'Bomb Gravity': {\n                        key: 'bombGravity',\n                        type: 'slider',\n                        min: 50,\n                        max: 200,\n                        step: 10\n                    },\n                    'Explosion Radius': {\n                        key: 'explosionRadius',\n                        type: 'slider',\n                        min: 5,\n                        max: 30,\n                        step: 1\n                    },\n                    'Building Density': {\n                        key: 'buildingDensity',\n                        type: 'slider',\n                        min: 0.3,\n                        max: 1.0,\n                        step: 0.1\n                    },\n                    'Max Building Height': {\n                        key: 'maxBuildingHeight',\n                        type: 'slider',\n                        min: 50,\n                        max: 150,\n                        step: 10\n                    },\n                    'Score Multiplier': {\n                        key: 'scoreMultiplier',\n                        type: 'slider',\n                        min: 1,\n                        max: 10,\n                        step: 1\n                    },\n                    'Camera Distance': {\n                        key: 'cameraDistance',\n                        type: 'slider',\n                        min: 50,\n                        max: 250,\n                        step: 10\n                    },\n                    'AI Plane Count': {\n                        key: 'aiPlaneCount',\n                        type: 'slider',\n                        min: 0,\n                        max: 10,\n                        step: 1\n                    },\n                    'Turret Ammo': {\n                        key: 'turretAmmo',\n                        type: 'slider',\n                        min: -1,\n                        max: 100,\n                        step: 1\n                    }\n                }\n            });\n\n            // ==================== RESIZE HANDLING ====================\n            function handleResize() {\n                const width = container.clientWidth;\n                const height = container.clientHeight;\n\n                camera.aspect = width / height;\n                camera.updateProjectionMatrix();\n\n                renderer.setSize(width, height);\n            }\n\n            window.addEventListener('resize', handleResize);\n            handleResize();\n\n            // ==================== MAIN GAME LOOP ====================\n            let lastTime = 0;\n\n            function gameLoop(timestamp) {\n                const deltaTime = Math.min((timestamp - lastTime) / 1000, 0.11);\n                lastTime = timestamp;\n\n                if (mode === 'play') {\n                    updatePlayer(deltaTime);\n                    updateLockOn();\n                    updateBombs(deltaTime);\n                    updateBullets(deltaTime);\n                    updateExplosions(deltaTime);\n                    updateJetStream(deltaTime);\n                    updateCoins(deltaTime);\n                    updateRainbowPlane(deltaTime);\n                    updateAIPlanes(deltaTime);\n                    updateCity();\n                    updateUI();\n                }\n\n                updateCamera();\n                renderer.render(scene, camera);\n\n                requestAnimationFrame(gameLoop);\n            }\n\n            // ==================== START GAME ====================\n            // Initialize game (async in play mode to load saved state)\n            initGame().then(() => {\n                createUI();\n\n                if (mode === 'edit') {\n                    // In edit mode, position camera for overview\n                    player.y = PLANE_ALTITUDE + 50;\n                    updateCamera();\n                }\n\n                requestAnimationFrame(gameLoop);\n            });\n        }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"planeSpeed\": 100,\n  \"turnSpeed\": 1.5,\n  \"planeColor\": 5066061,\n  \"bombCount\": -1,\n  \"bombGravity\": 120,\n  \"bombSize\": 1,\n  \"explosionRadius\": 50,\n  \"buildingDensity\": 0.6,\n  \"maxBuildingHeight\": 100,\n  \"scoreMultiplier\": 5,\n  \"showTrajectory\": false,\n  \"boostMultiplier\": 2,\n  \"cameraDistance\": 140,\n  \"purchasedSkins\": [\n    \"default\"\n  ],\n  \"currentSkin\": \"default\",\n  \"aiPlaneCount\": 8,\n  \"turretAmmo\": -1,\n  \"controlMode\": \"null\"\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Sky Strike** is a 3D aerial bombing game where players pilot a fighter plane over a procedurally-generated city environment. The core gameplay loop consists of:\n\n1. **Flight**: Player controls a plane flying at constant altitude over a 3D city\n2. **Target Identification**: Buildings and structures below serve as bombing targets\n3. **Bombing**: Player releases bombs that fall with realistic physics toward targets\n4. **Scoring**: Successfully hitting targets awards points; missed bombs have no penalty\n5. **Progression**: Score accumulates as player continues destroying targets\n\nThe gameplay is designed to be immediately accessible while offering depth through precision aiming and flight control mastery. The constant forward momentum creates urgency, while the 3D perspective provides spatial challenge.\n\n**Win Condition**: Reach target score thresholds to unlock achievements\n**Lose Condition**: None - this is an endless arcade experience focused on high scores\n\n## Starting Scope\n\nFor the initial implementation, focus on these core elements:\n\n**Must Have (First Iteration):**\n- 3D city environment with procedurally-placed buildings of varying heights\n- Player-controlled plane with smooth flight controls\n- Bomb dropping mechanic with physics-based trajectory\n- Collision detection between bombs and buildings\n- Score system with visual feedback\n- Simple UI showing score and bomb count\n- Touch controls (joystick) and keyboard/mouse support\n\n**Future Iterations:**\n- Multiple plane types with different characteristics\n- Special targets (fuel depots, bridges) worth bonus points\n- Obstacles to avoid (towers, balloons)\n- Power-ups (rapid fire bombs, guided missiles)\n- Time-based challenges or missions\n- Difficulty progression\n- Visual effects (explosions, smoke trails)\n\n## Game Perspective\n\n**3D Third-Person (Chase Camera)**\n\nThe game uses a third-person 3D perspective with the camera positioned behind and slightly above the plane, looking forward. This provides:\n- Clear view of the plane and its orientation\n- Good visibility of the city below for targeting\n- Sense of speed and altitude\n- Intuitive spatial awareness for bombing runs\n\nThe camera follows the plane smoothly, maintaining a fixed offset. The city scrolls beneath as the plane moves forward at constant speed.\n\n## Game World and Level Design\n\n**Environment Structure:**\n\nThe game world is an infinite procedurally-generated cityscape that scrolls beneath the player:\n\n- **City Grid**: Buildings arranged in a grid pattern with streets between them\n- **Building Variety**: 3-5 different building heights (low-rise, mid-rise, high-rise, skyscrapers)\n- **Building Density**: Varied density creates interesting targeting challenges\n- **Procedural Generation**: New city blocks generate ahead of the plane as old ones despawn behind\n- **Altitude**: Plane flies at fixed altitude (e.g., 200 units) above the city\n- **Boundaries**: Invisible side boundaries keep the plane within the city bounds\n\n**Visual Design:**\n- Clean, geometric buildings with flat colors\n- Grid-based street layout\n- Simple ground plane representing streets/ground\n- Sky gradient background (lighter at horizon, darker above)\n- Minimal but effective visual style focusing on gameplay clarity\n\n**Scale:**\n- Buildings: 20-100 units tall, 30-50 units wide\n- Streets: 20 units wide\n- Plane: ~15 units long\n- Visible area: ~500 units forward, 300 units wide\n\n## Edit Mode Design\n\nEdit mode allows players to customize their bombing experience:\n\n**Editable Properties:**\n\n1. **Plane Settings:**\n   - `planeSpeed`: Forward flight speed (50-200 units/sec)\n   - `turnSpeed`: How quickly plane responds to controls (0.5-3.0)\n   - `planeColor`: RGB color of the player's plane\n\n2. **Bomb Settings:**\n   - `bombCount`: Starting number of bombs (10-100, or infinite)\n   - `bombGravity`: How fast bombs fall (50-200)\n   - `bombSize`: Visual size of bombs (0.5-2.0)\n   - `explosionRadius`: Hit detection radius (5-30 units)\n\n3. **City Settings:**\n   - `buildingDensity`: How packed the city is (0.3-1.0)\n   - `maxBuildingHeight`: Tallest possible buildings (50-150 units)\n   - `cityColors`: Array of building colors to randomize from\n\n4. **Game Settings:**\n   - `scoreMultiplier`: Points per building hit (1-10)\n   - `showTrajectory`: Boolean to show bomb trajectory preview\n\n**Constraints:**\n- Plane speed minimum ensures game remains playable\n- Building heights capped to prevent impossible-to-hit targets\n- Bomb gravity prevents unrealistic physics\n- All numeric values have reasonable min/max ranges\n\n## Control Design\n\n**Sky Strike** uses a 3D third-person control scheme optimized for both mobile and desktop:\n\n**Mobile Controls:**\n- **Left Virtual Joystick**: Controls plane movement\n  - Left/Right: Bank and turn the plane horizontally\n  - Up/Down: Pitch the plane up/down (subtle altitude adjustment)\n  - Joystick appears where touched on left half of screen\n  - Analog input for smooth, responsive control\n- **Right Side Tap**: Drop bomb\n  - Large tap zone on right half of screen\n  - Visual button indicator in bottom-right corner\n  - Cooldown indicator prevents spam\n\n**Desktop Controls:**\n- **WASD / Arrow Keys**: \n  - A/D or Left/Right: Bank and turn plane\n  - W/S or Up/Down: Pitch plane up/down\n  - Analog-style acceleration (hold longer = more input)\n- **Spacebar or Left Mouse Click**: Drop bomb\n- **Mouse Movement**: Optional - subtle plane tilt following mouse position for enhanced control feel\n\n**Control Behavior:**\n- Plane has constant forward momentum (auto-accelerate)\n- Turning is smooth with momentum/inertia\n- Pitch control is subtle - plane maintains general altitude\n- Bombs inherit plane's forward velocity when dropped\n- All controls are responsive with immediate visual feedback\n\n**Input Handling:**\n- Track touch IDs to prevent interference between joystick and bomb button\n- Prevent default browser behavior for game keys\n- Clear input state on window blur\n- Normalize keyboard input to match joystick analog range\n\n## Art Style and Visual Theme\n\n**Low-Poly Military Aesthetic**\n\nThe game employs a clean, geometric art style that balances visual clarity with military theming:\n\n**Visual Style:**\n- Low-poly 3D models with flat shading\n- Bold, solid colors with minimal textures\n- Sharp geometric shapes emphasizing the angular nature of aircraft and buildings\n- Military color palette: grays, olive greens, steel blues\n- High contrast between plane, bombs, and city for gameplay clarity\n\n**Color Palette:**\n- **Plane**: Military gray/green with accent colors (red nose, insignia)\n- **Buildings**: Varied urban colors (concrete gray, glass blue, brick red, steel)\n- **Sky**: Gradient from light blue (horizon) to deeper blue (zenith)\n- **Bombs**: Dark metallic gray with yellow/red markings\n- **Explosions**: Bright orange/yellow with dark smoke\n\n**Asset Approach:**\n- **Generated Assets**: Plane model, bomb model, explosion sprite\n- **Code-Rendered**: Buildings (procedural boxes with WebGL), ground plane, sky gradient, UI elements\n- Buildings are simple colored boxes - no need for complex assets\n- Focus asset generation on the plane and effects for maximum visual impact\n\n**Technical Considerations:**\n- Use Three.js for 3D rendering\n- Buildings rendered as BoxGeometry with MeshBasicMaterial\n- Simple lighting (ambient + directional) for depth without complexity\n- Particle system for explosion effects\n- Consistent scale across all elements", "persistent_storage_config": null, "game_metadata": {"game_id": "01KQAQZ307J9M9QKCM00DFD93Z", "title": "City Bomber", "description": "Pilot a fighter plane over a 3D city, dropping bombs on strategic targets while mastering flight controls and precision aiming.", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_6c993615-af53-474c-8d82-e54d3984d784.webp", "created_at": 1777406355.0, "updated_at": 1777655858725.8687, "art_style": null}}