← Back to Articles Directory
AI Models • October 4, 2026 • 3 min read

Apodex 1.1 Mini Free: HTML Artifacts and Runtime Results

Three original browser artifacts from OpenRouter chat, with an SVG result, a globe import failure and retry, and a wizard game with a confirmed restart defect.

Co-Founder & Lead Programmer of AcceleratedLogic AI

Apodex 1.1 Mini (free) appeared in the supplied OpenRouter list on October 1, 2026. I submitted the same SVG crane, procedural globe, and wizard-game prompts used for our other creative-coding articles. By the end of the session, the route had delivered complete HTML for all three tasks, with retries on the two 3D tasks. Browser checks and source review found important differences between an attractive artifact and a working requirement.

Model and route facts

Checked October 3, 2026 OpenRouter route
Model ID apodex/apodex-1.1-mini:free
Input / output token price $0 / $0
Context 262,144 tokens
Inputs / output Text / text
Catalog parameters Reasoning, tools, streaming, structured output
The OpenRouter free listing is the route tested here. The Apodex technical report describes Mini as a 35B-parameter model. Apodex’s core-model API documentation distinguishes direct language-model access from its separate research-agent services. Our task run enabled no auxiliary OpenRouter tools, so it should be read as a browser-artifact generation check rather than a deep-research-agent evaluation.

Method and delivery record

Each prompt began in a fresh comparison room with Apodex and Ling 3.1 Flash selected. Provider choice was Auto. OpenRouter’s default system/sampling settings were retained, and all eight server tools were disabled. Apodex’s advanced panel showed Max tokens 0 (automatic) and Temperature 1.000; neither value was changed. The session manifest includes exact prompts, attempt counts, checks, and original-file hashes.

Test 1: Glass sphere and crane

The first SVG response was complete and rendered. It layers a sphere gradient, reflections, shadows, and a bird drawn with curved paths and ellipses. The glass composition is readable, but the rounded neck, head, wings, and body resemble a stylized bird more than folded paper. The result therefore meets the lightweight SVG-delivery requirement more convincingly than the detailed origami or photorealism requirements.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3D Glass Sphere with Origami Crane</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: radial-gradient(circle at center, #2b3242 0%, #1a1f2b 100%);
            font-family: sans-serif;
        }
        .container {
            width: 100%;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        svg {
            max-width: 90vmin;
            max-height: 90vmin;
            box-shadow: 0 20px 50px rgba(0,0,0,0.5);
        }
    </style>
</head>
<body>
    <div class="container">
        <svg viewBox="0 0 900 900" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
            <defs>
                <!-- Filter Definitions -->
                <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
                    <feGaussianBlur stdDeviation="3.5" result="coloredBlur"/>
                    <feMerge>
                        <feMergeNode in="coloredBlur"/>
                        <feMergeNode in="SourceGraphic"/>
                    </feMerge>
                </filter>

                <!-- Ambient/Contact Shadow for the sphere sitting on a surface -->
                <radialGradient id="shadowGrad" cx="0.5" cy="0.5" r="0.5">
                    <stop offset="0%" stop-color="#000000" stop-opacity="0.55"/>
                    <stop offset="70%" stop-color="#000000" stop-opacity="0.15"/>
                    <stop offset="100%" stop-color="#000000" stop-opacity="0"/>
                </radialGradient>

                <!-- The Glass Sphere Surface Gradients -->
                <radialGradient id="sphereMain" cx="0.35" cy="0.35" r="0.75">
                    <stop offset="10%" stop-color="#ffffff" stop-opacity="1"/>
                    <stop offset="30%" stop-color="#eef3f9" stop-opacity="0.95"/>
                    <stop offset="55%" stop-color="#9ab0cc" stop-opacity="0.55"/>
                    <stop offset="100%" stop-color="#4a6385" stop-opacity="0.9"/>
                </radialGradient>

                <!-- Deep inner refraction core (looks like the dense edge of glass) -->
                <radialGradient id="sphereCore" cx="0.35" cy="0.35" r="0.6">
                    <stop offset="0%" stop-color="#ffffff" stop-opacity="0.85"/>
                    <stop offset="45%" stop-color="#cfe0f2" stop-opacity="0.4"/>
                    <stop offset="100%" stop-color="#3a5270" stop-opacity="0.8"/>
                </radialGradient>

                <!-- Sharp white specular highlight (reflection) -->
                <radialGradient id="specularHighlight" cx="0.25" cy="0.25" r="0.45">
                    <stop offset="0%" stop-color="#ffffff" stop-opacity="0.95"/>
                    <stop offset="40%" stop-color="#ffffff" stop-opacity="0.3"/>
                    <stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
                </radialGradient>

                <!-- Secondary soft highlight reflection (bottom left of glass) -->
                <linearGradient id="secondaryHighlight" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%" stop-color="#ffffff" stop-opacity="0.1"/>
                    <stop offset="50%" stop-color="#ffffff" stop-opacity="0"/>
                </linearGradient>

                <!-- Origami Crane Gradients -->
                <radialGradient id="craneBodyGrad" cx="0.5" cy="0.5" r="0.5">
                    <stop offset="0%" stop-color="#f5f8fb"/>
                    <stop offset="60%" stop-color="#dce4ee"/>
                    <stop offset="100%" stop-color="#aebcce"/>
                </radialGradient>

                <linearGradient id="wingGrad" x1="0%" y1="0%" x2="100%" y2="0%">
                    <stop offset="0%" stop-color="#e8f0f9"/>
                    <stop offset="50%" stop-color="#c3d2e4"/>
                    <stop offset="100%" stop-color="#9db3ce"/>
                </linearGradient>

                <linearGradient id="darkFold" x1="0%" y1="0%" x2="100%" y2="100%">
                    <stop offset="0%" stop-color="#333f52"/>
                    <stop offset="100%" stop-color="#1a2332"/>
                </linearGradient>

                <linearGradient id="craneBeak" x1="0%" y1="0%" x2="100%" y2="0%">
                    <stop offset="0%" stop-color="#ffffff"/>
                    <stop offset="100%" stop-color="#d1dbe5"/>
                </linearGradient>
            </defs>

            <!-- Ambient Drop Shadow (contact shadow) -->
            <ellipse cx="450" cy="715" rx="195" ry="22" fill="url(#shadowGrad)"/>
            <ellipse cx="450" cy="720" rx="195" ry="22" fill="#000000" opacity="0.15" style="filter:blur(8px)"/>

            <!-- =================== THE GLASS SPHERE =================== -->

            <!-- Base Glass Body -->
            <circle cx="450" cy="460" r="210" fill="url(#sphereMain)"/>

            <!-- Inner Refraction Ring (simulates glass thickness and curvature) -->
            <circle cx="450" cy="460" r="188" fill="none" stroke="#ffffff" stroke-width="1.5" opacity="0.25"/>
            <circle cx="450" cy="460" r="160" fill="none" stroke="#3a5270" stroke-width="0.8" opacity="0.35"/>

            <!-- Background Refraction Lines (shows what's behind/distortion) -->
            <path d="M 260 280 Q 450 380 640 280" fill="none" stroke="#ffffff" stroke-width="2" opacity="0.12" stroke-linecap="round"/>
            <path d="M 250 420 Q 450 500 650 420" fill="none" stroke="#eaf0f8" stroke-width="1.5" opacity="0.2" stroke-linecap="round"/>
            <path d="M 245 560 Q 450 630 655 560" fill="none" stroke="#ffffff" stroke-width="1" opacity="0.15" stroke-linecap="round"/>

            <!-- The Crane (Drawn inside the sphere) -->
            <g id="origami-crane" transform="translate(0, 10)">
                <!-- Tail (Back) -->
                <path d="M 450 630 L 450 590 L 435 560 L 450 600 L 465 560 L 450 590 L 450 630 Z" fill="url(#craneBodyGrad)" stroke="#8c9db3" stroke-width="1"/>

                <!-- Lower Body / Legs base -->
                <path d="M 430 640 Q 450 655 470 640 L 470 660 Q 450 670 430 660 Z" fill="url(#craneBodyGrad)" stroke="#7a8ba1" stroke-width="1"/>

                <!-- Main Body -->
                <path d="M 430 640 C 420 600 440 540 475 535 C 490 580 485 630 470 660 C 450 655 440 645 430 640 Z" fill="url(#craneBodyGrad)" stroke="#7a8ba1" stroke-width="1"/>
                
                <!-- Body Fold (3D crease on back of bird) -->
                <path d="M 430 640 C 420 600 440 540 475 535" fill="none" stroke="url(#darkFold)" stroke-width="2" opacity="0.55" stroke-linecap="round"/>

                <!-- Neck (Curved stroke) -->
                <path d="M 460 530 Q 490 460 470 380" fill="none" stroke="url(#craneBodyGrad)" stroke-width="18" stroke-linecap="round" opacity="0.95"/>
                <path d="M 460 530 Q 490 460 470 380" fill="none" stroke="#7a8ba1" stroke-width="1" opacity="0.5"/>

                <!-- Neck Fold Shadow -->
                <path d="M 465 525 Q 490 470 472 400" fill="none" stroke="#7a8ba1" stroke-width="6" stroke-linecap="round" opacity="0.35"/>

                <!-- Head -->
                <ellipse cx="472" cy="360" rx="13" ry="13" fill="url(#craneBodyGrad)" stroke="#7a8ba1" stroke-width="1"/>
                <!-- Head fold lines -->
                <path d="M 468 355 L 478 365 M 472 350 L 466 352" stroke="#7a8ba1" stroke-width="1.2" opacity="0.5"/>

                <!-- Beak -->
                <path d="M 482 356 L 494 364 L 484 362 Z" fill="url(#craneBeak)" stroke="#7a8ba1" stroke-width="0.8"/>
                <path d="M 483 358 L 488 361 L 484 360 Z" fill="#000000" opacity="0.7"/>

                <!-- Left Wing (Back/Far) -->
                <path d="M 450 535 C 400 510 340 500 320 460 C 340 450 360 470 380 485 C 410 500 430 520 450 535 Z" fill="url(#wingGrad)" stroke="#8c9db3" stroke-width="1.5"/>
                <!-- Wing veins/folds -->
                <path d="M 330 470 L 390 495 M 350 485 L 415 510 M 370 500 L 435 530" stroke="#7a8ba1" stroke-width="1.2" opacity="0.6" stroke-linecap="round"/>
                
                <!-- Left Wing Tip Highlight -->
                <ellipse cx="322" cy="458" rx="8" ry="5" fill="url(#specularHighlight)" transform="rotate(-20 322 458)"/>

                <!-- Right Wing (Front/Near) -->
                <path d="M 450 535 C 500 510 560 500 580 460 C 560 450 540 470 520 485 C 490 500 470 520 450 535 Z" fill="url(#wingGrad)" stroke="#8c9db3" stroke-width="1.5"/>
                <!-- Wing veins/folds -->
                <path d="M 570 470 L 510 495 M 550 485 L 485 510 M 530 500 L 465 530" stroke="#7a8ba1" stroke-width="1.2" opacity="0.6" stroke-linecap="round"/>
                
                <!-- Right Wing Tip Highlight -->
                <ellipse cx="578" cy="458" rx="8" ry="5" fill="url(#specularHighlight)" transform="rotate(20 578 458)"/>

                <!-- Right Wing Secondary Fold (creates 3D volume) -->
                <path d="M 520 485 L 500 515 L 480 505" fill="none" stroke="#7a8ba1" stroke-width="2" opacity="0.4" stroke-linejoin="round"/>
            </g>

            <!-- =================== REFRACTION & SHADOW INSIDE/BELOW BIRD =================== -->
            <!-- Bird's own shadow on the bottom of the glass -->
            <ellipse cx="450" cy="680" rx="60" ry="15" fill="#1a2332" opacity="0.25" filter="blur(10px)"/>

            <!-- =================== HIGHLIGHTS & REFLECTIONS ON GLASS =================== -->
            
            <!-- Top Sharp Specular Highlight -->
            <path d="M 350 300 C 330 330 390 390 430 360 C 450 340 470 340 480 320" fill="none" stroke="url(#specularHighlight)" stroke-width="4" stroke-linecap="round" opacity="0.9"/>
            
            <!-- Secondary bottom-left reflection on glass -->
            <path d="M 300 600 C 330 640 420 680 490 650" fill="none" stroke="url(#secondaryHighlight)" stroke-width="35" stroke-linecap="round" opacity="0.5" filter="blur(6px)"/>

            <!-- Thin outer glass edge ring -->
            <circle cx="450" cy="460" r="210" fill="none" stroke="#ffffff" stroke-width="0.6" opacity="0.4"/>

            <!-- Final Refraction/Reflection Overlay (glass thickness) -->
            <circle cx="450" cy="460" r="210" fill="none" stroke="url(#secondaryHighlight)" stroke-width="30" stroke-linecap="round" opacity="0.15" style="filter:blur(4px)"/>

        </svg>
    </div>
</body>
</html>
The UI displayed $0, an estimated 4,506 tokens, and 16.3 seconds for that response. These estimates are reported to describe the session and are not a general latency guarantee.

Test 2: Procedural globe

The first complete globe file did not initialize WebGL. Its OrbitControls module imported bare "three", but the document supplied no import map to resolve that name. The browser reported: Failed to resolve module specifier "three". Only the static speed slider appeared, and no canvas was created. The original failing file is preserved separately.
A whole-turn same-prompt retry, initially made for Ling’s rate-limit failure, also regenerated Apodex’s globe. That second output included an import map and rendered a WebGL globe, atmosphere, stars, and particle rings. I changed the speed control from 0.50 to 0.51 and exercised camera input. The inspected surface looked predominantly beige and speckled, so the requested continent shapes were difficult to distinguish. No artifact-script error was observed on the retry; zoom was not measured quantitatively.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Interactive 3D Procedural Globe</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background-color: #000008;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        canvas {
            display: block;
        }
        #controls {
            position: absolute;
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            background: linear-gradient(135deg, rgba(20, 30, 60, 0.85), rgba(10, 15, 30, 0.9));
            padding: 14px 30px;
            border-radius: 40px;
            color: #ffffff;
            display: flex;
            align-items: center;
            gap: 20px;
            z-index: 100;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(100, 150, 255, 0.3);
            box-shadow: 0 4px 30px rgba(0, 100, 255, 0.2);
        }
        #controls label {
            font-size: 14px;
            letter-spacing: 0.5px;
            color: #a8c7ff;
            min-width: 140px;
            text-align: right;
        }
        #controls .value-display {
            font-size: 14px;
            font-weight: bold;
            color: #7fffd4;
            min-width: 45px;
        }
        #speedSlider {
            -webkit-appearance: none;
            appearance: none;
            width: 220px;
            height: 8px;
            border-radius: 4px;
            background: linear-gradient(90deg, #1a2a5c, #0d1b3a);
            outline: none;
            cursor: pointer;
        }
        #speedSlider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: linear-gradient(135deg, #4facfe, #00f2fe);
            cursor: pointer;
            box-shadow: 0 0 15px rgba(0, 242, 254, 0.6);
        }
        #speedSlider::-moz-range-thumb {
            width: 20px;
            height: 20px;
            border-radius: 50%;
            background: linear-gradient(135deg, #4facfe, #00f2fe);
            cursor: pointer;
            border: none;
            box-shadow: 0 0 15px rgba(0, 242, 254, 0.6);
        }
        #instructions {
            position: absolute;
            top: 20px;
            left: 20px;
            color: rgba(168, 199, 255, 0.7);
            font-size: 13px;
            z-index: 100;
            pointer-events: none;
        }
        #instructions h3 {
            color: #ffffff;
            font-size: 15px;
            margin-bottom: 6px;
        }
        #instructions p {
            margin: 3px 0;
        }
    </style>

    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/[email protected]/build/three.module.js",
                "three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
            }
        }
    </script>
</head>
<body>
    <div id="instructions">
        <h3>Procedural 3D Globe</h3>
        <p>🖱️ Drag to rotate · 📜 Scroll to zoom · ➖➕ Adjust rotation speed</p>
    </div>

    <div id="controls">
        <label for="speedSlider">ROTATION SPEED</label>
        <input type="range" id="speedSlider" min="0" max="2" step="0.01" value="0.5">
        <span class="value-display" id="speedValue">0.50</span>
    </div>

    <script type="module">
        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        // ---------- Scene Setup ----------
        const scene = new THREE.Scene();
        scene.fog = new THREE.FogExp2(0x000008, 0.012);

        const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
        camera.position.set(0, 6, 24);

        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        renderer.toneMapping = THREE.ReinhardToneMapping;
        document.body.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.06;
        controls.enableZoom = true;
        controls.enablePan = false;
        controls.minDistance = 9;
        controls.maxDistance = 50;

        // ---------- Procedural Noise Function ----------
        const pseudoRandom = (x, y, z = 0) => {
            const n = Math.sin(x * 12.9898 + y * 78.233 + z * 94.3211) * 43758.5453;
            return Math.abs(n - Math.floor(n));
        };

        // ---------- Generate Procedural Globe Texture ----------
        function createGlobeTexture() {
            const size = 2048;
            const canvas = document.createElement('canvas');
            canvas.width = size;
            canvas.height = size;
            const ctx = canvas.getContext('2d');

            const imageData = ctx.createImageData(size, size);
            const data = imageData.data;

            const noise = (x, y, octaves = 6) => {
                let n = 0;
                let amplitude = 1;
                let frequency = 1;
                const maxAmpSum = 2;
                for (let i = 0; i < octaves; i++) {
                    n += pseudoRandom(x * frequency * 0.004, y * frequency * 0.004, i * 7) * amplitude;
                    amplitude *= 0.5;
                    frequency *= 2;
                }
                return n / (maxAmpSum - 1);
            };

            const smoothStep = (t) => t * t * (3 - 2 * t);

            for (let y = 0; y < size; y++) {
                for (let x = 0; x < size; x++) {
                    const base = noise(x, y);
                    const idx = (y * size + x) * 4;

                    // Island/continent mask
                    const island = base > 0.52;

                    if (island) {
                        const elev = smoothStep((base - 0.4) * 2.5);
                        const hue = 0.10 + pseudoRandom(x, y, 42) * 0.06;
                        const sat = 0.35 + elev * 0.45;
                        const light = 0.14 + elev * 0.42;
                        data.set(hslToRgb(hue, sat, light), idx);
                        data[idx + 3] = 255;
                    } else {
                        // Ocean gradient based on noise (shallow near land)
                        const depth = pseudoRandom(x, y, 88);
                        const rgb = oceanColor(depth, base < 0.56);
                        data.set(rgb, idx);
                        data[idx + 3] = 255;
                    }
                }
            }

            ctx.putImageData(imageData, 0, 0);
            const texture = new THREE.CanvasTexture(canvas);
            texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
            return texture;
        }

        const hslToRgb = (h, s, l) => {
            let r, g, b;
            if (s === 0) {
                r = g = b = l;
            } else {
                const hue2rgb = (p, q, t) => {
                    if (t < 0) t += 1;
                    if (t > 1) t -= 1;
                    if (t < 1/6) return p + (q - p) * 6 * t;
                    if (t < 1/2) return q;
                    if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
                    return p;
                };
                const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
                const p = 2 * l - q;
                r = hue2rgb(p, q, h + 1/3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1/3);
            }
            return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
        };

        const oceanColor = (depth, shallow) => {
            if (shallow) {
                const brightness = 0.3 + depth * 0.5;
                return [
                    Math.round(20 + brightness * 60),
                    Math.round(90 + brightness * 40),
                    Math.round(150 + brightness * 60)
                ];
            }
            return [
                Math.round(12 + depth * 30),
                Math.round(40 + depth * 70),
                Math.round(110 + depth * 120)
            ];
        };

        const globeTexture = createGlobeTexture();

        // ---------- Globe ----------
        const globeGeometry = new THREE.SphereGeometry(6, 96, 96);
        const globeMaterial = new THREE.MeshStandardMaterial({
            map: globeTexture,
            roughness: 0.85,
            metalness: 0.05
        });
        const globe = new THREE.Mesh(globeGeometry, globeMaterial);
        scene.add(globe);

        // ---------- Translucent Atmosphere Glow ----------
        const atmoMaterial = (radius, color, opacity, scale) => {
            return new THREE.Mesh(
                new THREE.SphereGeometry(radius, 96, 96),
                new THREE.MeshPhongMaterial({
                    color: color,
                    transparent: true,
                    opacity: opacity,
                    side: THREE.BackSide,
                    blending: THREE.AdditiveBlending,
                    depthWrite: false
                })
            );
        };

        const atmoInner = atmoMaterial(6.45, 0x22aaff, 0.35, 1);
        const atmoOuter = atmoMaterial(6.9, 0x88ccff, 0.12, 1);
        globe.add(atmoInner);
        globe.add(atmoOuter);

        // ---------- Lighting ----------
        const ambientLight = new THREE.AmbientLight(0x445577, 0.7);
        scene.add(ambientLight);

        const sunLight = new THREE.DirectionalLight(0xfff5e6, 2.4);
        sunLight.position.set(20, 10, 20);
        scene.add(sunLight);

        const rimLight = new THREE.SpotLight(0x66aaff, 3);
        rimLight.position.set(-15, -5, -10);
        rimLight.angle = Math.PI / 4;
        rimLight.penumbra = 0.6;
        scene.add(rimLight);

        // ---------- Starfield ----------
        const starGeometry = new THREE.BufferGeometry();
        const starCount = 5000;
        const starPositions = new Float32Array(starCount * 3);
        const starSizes = new Float32Array(starCount);
        const starColors = new Float32Array(starCount * 3);

        for (let i = 0; i < starCount; i++) {
            const i3 = i * 3;
            const radius = 120 + Math.random() * 180;
            const theta = Math.random() * Math.PI * 2;
            const phi = Math.acos(2 * Math.random() - 1);
            starPositions[i3] = radius * Math.sin(phi) * Math.cos(theta);
            starPositions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta);
            starPositions[i3 + 2] = radius * Math.cos(phi);
            starSizes[i] = Math.random() * 1.8 + 0.4;
            const colorType = Math.random();
            if (colorType < 0.7) {
                starColors[i3] = 0.9; starColors[i3 + 1] = 0.9; starColors[i3 + 2] = 1;
            } else if (colorType < 0.85) {
                starColors[i3] = 1; starColors[i3 + 1] = 0.92; starColors[i3 + 2] = 0.8;
            } else {
                starColors[i3] = 0.85; starColors[i3 + 1] = 0.9; starColors[i3 + 2] = 1;
            }
        }

        starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
        starGeometry.setAttribute('size', new THREE.BufferAttribute(starSizes, 1));
        starGeometry.setAttribute('color', new THREE.BufferAttribute(starColors, 3));

        const starMaterial = new THREE.PointsMaterial({
            size: 0.9,
            transparent: true,
            opacity: 0.85,
            sizeAttenuation: true,
            blending: THREE.AdditiveBlending
        });
        const stars = new THREE.Points(starGeometry, starMaterial);
        scene.add(stars);

        // ---------- Orbiting Ring of Light Particles ----------
        const particleCount = 600;
        const particleGeometry = new THREE.BufferGeometry();
        const pPositions = new Float32Array(particleCount * 3);
        const pSizes = new Float32Array(particleCount);
        const pPhases = new Float32Array(particleCount);
        const pSpeeds = new Float32Array(particleCount);
        const pHeights = new Float32Array(particleCount);
        const pRadii = new Float32Array(particleCount);
        const pColors = new Float32Array(particleCount * 3);

        for (let i = 0; i < particleCount; i++) {
            pPhases[i] = Math.random() * Math.PI * 2;
            pSpeeds[i] = 0.25 + Math.random() * 0.35;
            const ringLevel = Math.floor(Math.random() * 4);
            const tilt = (Math.random() - 0.5) * 0.6;
            const inclination = (Math.random() - 0.5) * (0.35 * ringLevel);
            pHeights[i] = tilt + Math.sin(i) * 0.12 * ringLevel;
            pRadii[i] = 6.5 + ringLevel * 0.45 + pHeights[i] * 0.2;
            const theta = Math.random() * Math.PI * 2;
            pPositions[i * 3] = pRadii[i] * Math.cos(theta);
            pPositions[i * 3 + 1] = pHeights[i];
            pPositions[i * 3 + 2] = pRadii[i] * Math.sin(theta);
            pSizes[i] = Math.random() * 2.2 + 0.8;
            const colorHue = 0.52 + Math.random() * 0.12;
            const c = new THREE.Color();
            c.setHSL(colorHue, 0.95, 0.55 + Math.random() * 0.3);
            pColors[i * 3] = c.r;
            pColors[i * 3 + 1] = c.g;
            pColors[i * 3 + 2] = c.b;
        }

        particleGeometry.setAttribute('position', new THREE.BufferAttribute(pPositions, 3));
        particleGeometry.setAttribute('size', new THREE.BufferAttribute(pSizes, 1));
        particleGeometry.setAttribute('phase', new THREE.BufferAttribute(pPhases, 1));
        particleGeometry.setAttribute('speed', new THREE.BufferAttribute(pSpeeds, 1));
        particleGeometry.setAttribute('height', new THREE.BufferAttribute(pHeights, 1));
        particleGeometry.setAttribute('radius', new THREE.BufferAttribute(pRadii, 1));
        particleGeometry.setAttribute('color', new THREE.BufferAttribute(pColors, 3));

        const particleMaterial = new THREE.ShaderMaterial({
            uniforms: {
                time: { value: 0 },
                globalSpeed: { value: 1 }
            },
            vertexShader: `
                attribute float size;
                attribute float phase;
                attribute float speed;
                attribute float height;
                attribute float radius;
                attribute vec3 color;
                varying vec3 vColor;
                uniform float time;
                uniform float globalSpeed;
                void main() {
                    vColor = color;
                    float t = time * globalSpeed + phase;
                    float x = radius * cos(t);
                    float z = radius * sin(t);
                    vec3 pos = vec3(x, height, z);
                    vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
                    gl_Position = projectionMatrix * mvPosition;
                    gl_PointSize = size * (26.0 / -mvPosition.z);
                }
            `,
            fragmentShader: `
                varying vec3 vColor;
                void main() {
                    float d = length(gl_PointCoord - vec2(0.5));
                    if (d > 0.5) discard;
                    float alpha = 1.0 - smoothstep(0.2, 0.5, d);
                    gl_FragColor = vec4(vColor, alpha * 0.85);
                }
            `,
            transparent: true,
            blending: THREE.AdditiveBlending,
            depthWrite: false
        });

        const orbitRing = new THREE.Points(particleGeometry, particleMaterial);
        scene.add(orbitRing);

        // ---------- UI Controls ----------
        const speedSlider = document.getElementById('speedSlider');
        const speedValue = document.getElementById('speedValue');
        let rotationSpeed = 0.0005;

        const updateSpeed = (value) => {
            rotationSpeed = value * 0.001;
            speedValue.textContent = value.toFixed(2);
        };
        updateSpeed(parseFloat(speedSlider.value));

        speedSlider.addEventListener('input', (e) => {
            updateSpeed(parseFloat(e.target.value));
        });

        // ---------- Animation Loop ----------
        const clock = new THREE.Clock();

        function animate() {
            requestAnimationFrame(animate);
            const delta = clock.getDelta();

            globe.rotation.y += rotationSpeed;
            atmoInner.rotation.y += rotationSpeed * 0.85;
            orbitRing.material.uniforms.time.value += delta;

            controls.update();
            renderer.render(scene, camera);
        }

        animate();

        // ---------- Window Resize ----------
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
    </script>
</body>
</html>

Test 3: Crystal Mage — Sky Archipelago

The first game stream ended with a network error and incomplete code. A focused retry produced Crystal Mage: Sky Archipelago. Its scene rendered floating islands, a low-poly wizard, crystals, environmental particles, lighting changes, and a health/score HUD. Starting the game hid the opening screen. My short interaction check ended at 0 of 12 crystals; sustained movement, collection, combat, audible sound, and a full victory were not verified end to end.
Source review found a concrete restart defect: PLAY AGAIN calls restartGame, which hides the game-over panel and shows the start screen without invoking resetGame. Starting again also leaves gameOver and the old score/health unchanged. An isolated probe using the actual generated functions confirmed that a finished-round state stayed gameOver=true with health 0 and its prior score after restart/start. This is a source-level check, not a claim that a full loss/win was played through in the browser.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Crystal Mage: Sky Archipelago</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      overflow: hidden;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    }
    #game-container {
      width: 100vw;
      height: 100vh;
      position: relative;
    }
    #hud {
      position: absolute;
      top: 20px;
      left: 20px;
      color: white;
      text-shadow: 2px 2px 4px rgba(0,0,0,0.4);
      z-index: 10;
    }
    .hud-panel {
      background: rgba(0,0,0,0.35);
      backdrop-filter: blur(8px);
      border-radius: 12px;
      padding: 14px 20px;
      margin-bottom: 12px;
      border: 1px solid rgba(255,255,255,0.15);
    }
    .hud-row {
      display: flex;
      align-items: center;
      gap: 14px;
      margin: 6px 0;
    }
    .hud-label {
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 1px;
      font-size: 12px;
      color: #ffd89f;
    }
    .hud-value {
      font-size: 20px;
      font-weight: bold;
    }
    #health-bar-container {
      width: 200px;
      height: 16px;
      background: rgba(0,0,0,0.4);
      border-radius: 8px;
      overflow: hidden;
      border: 1px solid rgba(255,255,255,0.2);
    }
    #health-bar {
      width: 100%;
      height: 100%;
      background: linear-gradient(90deg, #ff416c, #ff4b2b);
      transition: width 0.25s ease;
    }
    #crystal-bar-container {
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .crystal-icon {
      font-size: 18px;
      animation: float 1.5s ease-in-out infinite;
    }
    @keyframes float {
      0%, 100% { transform: translateY(0); }
      50% { transform: translateY(-4px); }
    }
    #instructions {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      color: white;
      text-shadow: 1px 1px 3px rgba(0,0,0,0.5);
      text-align: center;
      z-index: 10;
      font-size: 13px;
      background: rgba(0,0,0,0.3);
      padding: 10px 24px;
      border-radius: 8px;
    }
    #game-over, #start-screen {
      position: absolute;
      inset: 0;
      background: rgba(15, 23, 42, 0.93);
      backdrop-filter: blur(10px);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      z-index: 100;
      color: white;
    }
    #game-over h1, #start-screen h1 {
      font-size: 52px;
      margin-bottom: 16px;
      background: linear-gradient(45deg, #ffd89f, #ff6b6b);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      background-clip: text;
    }
    #game-over h1 { -webkit-text-fill-color: white; background: none; }
    .result-stats {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 24px;
      margin: 30px 0;
      text-align: center;
    }
    .stat-box {
      background: rgba(255,255,255,0.1);
      padding: 20px 30px;
      border-radius: 12px;
      border: 1px solid rgba(255,255,255,0.15);
    }
    .stat-label {
      font-size: 13px;
      text-transform: uppercase;
      letter-spacing: 1.5px;
      color: #94a3b8;
      margin-bottom: 6px;
    }
    .stat-number {
      font-size: 36px;
      font-weight: bold;
      color: #ffd89f;
    }
    button {
      background: linear-gradient(45deg, #6c5ce7, #a29bfe);
      border: none;
      padding: 16px 48px;
      font-size: 20px;
      font-weight: bold;
      color: white;
      border-radius: 50px;
      cursor: pointer;
      transition: transform 0.2s, box-shadow 0.2s;
      margin-top: 10px;
    }
    button:hover {
      transform: scale(1.06);
      box-shadow: 0 8px 24px rgba(108, 92, 231, 0.5);
    }
    #start-screen h2 {
      font-size: 24px;
      color: #cbd5e1;
      margin-bottom: 30px;
    }
    .hidden { display: none !important; }
    #objective {
      font-size: 16px;
      color: #81e6d9;
      margin-top: 8px;
    }
  </style>
</head>
<body>
  <div id="game-container">
    <div id="hud">
      <div class="hud-panel">
        <div class="hud-row">
          <span class="hud-label">Health</span>
          <div id="health-bar-container">
            <div id="health-bar"></div>
          </div>
        </div>
        <div class="hud-row">
          <span class="hud-label">Score</span>
          <span class="hud-value" id="score">0</span>
        </div>
        <div class="hud-row">
          <span class="hud-label">Crystals</span>
          <div id="crystal-bar-container">
            <span class="crystal-icon">💎</span>
            <span class="hud-value" id="crystals">0 / 0</span>
          </div>
        </div>
        <div id="objective">Explore the floating islands and collect all crystals</div>
      </div>
    </div>

    <div id="instructions">
      WASD to move · SPACE to jump · Mouse drag to rotate camera · Scroll to zoom
    </div>

    <div id="start-screen">
      <h1>CRYSTAL MAGE</h1>
      <h2>Sky Archipelago Adventure</h2>
      <div class="result-stats">
        <div class="stat-box">
          <div class="stat-label">Objective</div>
          <div class="stat-number" style="font-size:22px">Collect all crystals</div>
        </div>
        <div class="stat-box">
          <div class="stat-label">Avoid</div>
          <div class="stat-number" style="font-size:22px">Elemental turrets</div>
        </div>
        <div class="stat-box">
          <div class="stat-label">Style</div>
          <div class="stat-number" style="font-size:22px">Pastel worlds</div>
        </div>
      </div>
      <button id="start-btn">START ADVENTURE</button>
    </div>

    <div id="game-over" class="hidden">
      <h1>GAME OVER</h1>
      <div class="result-stats">
        <div class="stat-box">
          <div class="stat-label">Final Score</div>
          <div class="stat-number" id="final-score">0</div>
        </div>
        <div class="stat-box">
          <div class="stat-label">Crystals</div>
          <div class="stat-number" id="final-crystals">0 / 0</div>
        </div>
        <div class="stat-box">
          <div class="stat-label">Survival Time</div>
          <div class="stat-number" id="final-time">0s</div>
        </div>
      </div>
      <button id="restart-btn">PLAY AGAIN</button>
    </div>
  </div>

  <script type="module">
    import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';

    // ============ CONFIGURATION ============
    const CONFIG = {
      playerSpeed: 8,
      jumpForce: 14,
      gravity: 28,
      cameraDistance: 16,
      cameraMinDistance: 8,
      cameraMaxDistance: 30,
      turretRange: 22,
      turretFireRate: 1.4,
      projectileSpeed: 22,
      dayLength: 120,
      crystalPoints: 100,
      crystalTotal: 12,
      turretCount: 8,
      treeCount: 24,
      rockCount: 30,
      cloudCount: 14
    };

    // ============ GLOBAL STATE ============
    const state = {
      score: 0,
      health: 100,
      crystalsCollected: 0,
      crystalsTotal: CONFIG.crystalTotal,
      gameOver: false,
      won: false,
      startTime: 0,
      elapsedTime: 0,
      isPaused: true
    };

    // Input state
    const keys = {};
    const mouse = { x: 0, y: 0, isDown: false, lastX: 0, lastY: 0 };
    const cameraAngle = { yaw: Math.PI, pitch: 0.3 };

    // Scene graph groups
    let scene, camera, renderer;
    let player, playerMesh, shadowPlane;
    let islandMeshes = [];
    let crystalMeshes = [];
    let treeMeshes = [];
    let rockMeshes = [];
    let cloudMeshes = [];
    let turrets = [];
    let projectiles = [];
    let particles = [];
    let clock = new THREE.Clock();

    // Camera controls
    let cameraTarget = new THREE.Vector3();
    let cameraOffset = new THREE.Vector3();
    let cameraSmoothFactor = 0.08;

    // Lighting
    let ambientLight, directionalLight, hemiLight;
    let lightTimer = 0;

    // Audio
    let audioCtx = null;

    // ============ UTILITY FUNCTIONS ============
    function randomInRange(min, max) { return Math.random() * (max - min) + min; }
    function randomPastelColor() {
      const colors = [
        0xFFB7B2, 0xFFDAC1, 0xE2F0CB, 0xB5EAD7, 0xC7CEEA,
        0xF6EAC0, 0xE0BBE4, 0xD4F0F0, 0xFEDED7, 0xD1FAE5
      ];
      return colors[Math.floor(Math.random() * colors.length)];
    }
    function distance(a, b) {
      return Math.sqrt((a.x-b.x)**2 + (a.y-b.y)**2 + (a.z-b.z)**2);
    }
    function clamp(value, min, max) {
      return Math.max(min, Math.min(max, value));
    }
    function lerp(a, b, t) { return a + (b - a) * t; }

    // ============ AUDIO SYSTEM (Web Audio API) ============
    function initAudio() {
      if (audioCtx) return;
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      audioCtx = new AudioContext();
    }

    function playSound(type) {
      if (!audioCtx || audioCtx.state === 'suspended') return;
      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();
      osc.connect(gain);
      gain.connect(audioCtx.destination);

      const now = audioCtx.currentTime;
      switch (type) {
        case 'jump':
          osc.type = 'sine';
          osc.frequency.setValueAtTime(300, now);
          osc.frequency.exponentialRampToValueAtTime(500, now + 0.1);
          gain.gain.setValueAtTime(0.3, now);
          gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
          osc.start(now);
          osc.stop(now + 0.15);
          break;
        case 'collect':
          osc.type = 'triangle';
          osc.frequency.setValueAtTime(600, now);
          osc.frequency.setValueAtTime(900, now + 0.1);
          osc.frequency.setValueAtTime(1200, now + 0.2);
          gain.gain.setValueAtTime(0.25, now);
          gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
          osc.start(now);
          osc.stop(now + 0.3);
          break;
        case 'hit':
          osc.type = 'sawtooth';
          osc.frequency.setValueAtTime(150, now);
          osc.frequency.exponentialRampToValueAtTime(60, now + 0.3);
          gain.gain.setValueAtTime(0.3, now);
          gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
          osc.start(now);
          osc.stop(now + 0.3);
          break;
        case 'shoot':
          osc.type = 'square';
          osc.frequency.setValueAtTime(400, now);
          osc.frequency.exponentialRampToValueAtTime(100, now + 0.15);
          gain.gain.setValueAtTime(0.08, now);
          gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
          osc.start(now);
          osc.stop(now + 0.15);
          break;
        case 'gameover':
          osc.type = 'sine';
          osc.frequency.setValueAtTime(400, now);
          osc.frequency.linearRampToValueAtTime(200, now + 1);
          gain.gain.setValueAtTime(0.3, now);
          gain.gain.linearRampToValueAtTime(0.01, now + 1.5);
          osc.start(now);
          osc.stop(now + 1.5);
          break;
        case 'win':
          osc.type = 'triangle';
          for (let i = 0; i < 5; i++) {
            osc.frequency.setValueAtTime(400 + i * 150, now + i * 0.12);
          }
          gain.gain.setValueAtTime(0.2, now);
          gain.gain.linearRampToValueAtTime(0.01, now + 1);
          osc.start(now);
          osc.stop(now + 1);
          break;
      }
    }

    // ============ SCENE SETUP ============
    function initScene() {
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0x87CEEB);
      scene.fog = new THREE.Fog(0x87CEEB, 40, 120);

      camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.3, 400);

      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
      renderer.shadowMap.enabled = true;
      renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      document.getElementById('game-container').appendChild(renderer.domElement);

      // Lighting groups
      ambientLight = new THREE.AmbientLight(0xffffff, 0.45);
      scene.add(ambientLight);

      hemiLight = new THREE.HemisphereLight(0xd6c0ff, 0x4a5a4a, 0.5);
      scene.add(hemiLight);

      directionalLight = new THREE.DirectionalLight(0xffeeb1, 1);
      directionalLight.position.set(50, 80, 30);
      directionalLight.castShadow = true;
      directionalLight.shadow.mapSize.width = 2048;
      directionalLight.shadow.mapSize.height = 2048;
      directionalLight.shadow.camera.near = 0.5;
      directionalLight.shadow.camera.far = 200;
      directionalLight.shadow.camera.left = -100;
      directionalLight.shadow.camera.right = 100;
      directionalLight.shadow.camera.top = 100;
      directionalLight.shadow.camera.bottom = -100;
      scene.add(directionalLight);
    }

    // ============ PLAYER ============
    function createPlayer() {
      player = new THREE.Group();
      player.position.set(0, 5, 0);

      const materials = {
        skin: new THREE.MeshLambertMaterial({ color: 0xffd9b3 }),
        robe: new THREE.MeshLambertMaterial({ color: 0x6c5ce7 }),
        trim: new THREE.MeshLambertMaterial({ color: 0xff6b6b }),
        hat: new THREE.MeshLambertMaterial({ color: 0x3f37c9 }),
        staff: new THREE.MeshLambertMaterial({ color: 0x8b4513 })
      };

      // Body (robe)
      const body = new THREE.Mesh(
        new THREE.CylinderGeometry(0.35, 0.45, 0.9, 8),
        materials.robe
      );
      body.position.y = 0.5;
      body.castShadow = true;
      player.add(body);

      // Trim belt
      const belt = new THREE.Mesh(
        new THREE.TorusGeometry(0.4, 0.05, 8, 20),
        materials.trim
      );
      belt.rotation.x = Math.PI / 2;
      belt.position.y = 0.65;
      player.add(belt);

      // Head
      const head = new THREE.Mesh(
        new THREE.SphereGeometry(0.28, 12, 12),
        materials.skin
      );
      head.position.y = 1.05;
      head.castShadow = true;
      player.add(head);

      // Eyes
      const eye = new THREE.Mesh(
        new THREE.SphereGeometry(0.05, 6, 6),
        new THREE.MeshBasicMaterial({ color: 0x2d3436 })
      );
      eye.position.set(0.1, 1.1, 0.22);
      player.add(eye);
      const eye2 = eye.clone();
      eye2.position.set(-0.1, 1.1, 0.22);
      player.add(eye2);

      // Pointy hat
      const hat = new THREE.Mesh(
        new THREE.ConeGeometry(0.32, 0.7, 8),
        materials.hat
      );
      hat.position.y = 1.55;
      hat.castShadow = true;
      player.add(hat);

      // Hat trim
      const hatTrim = new THREE.Mesh(
        new THREE.TorusGeometry(0.31, 0.04, 6, 16),
        materials.trim
      );
      hatTrim.rotation.x = Math.PI / 2;
      hatTrim.position.y = 1.8;
      player.add(hatTrim);

      // Staff
      const staff = new THREE.Mesh(
        new THREE.CylinderGeometry(0.03, 0.03, 1.6, 6),
        materials.staff
      );
      staff.position.set(0.42, 0.6, 0.35);
      staff.rotation.z = -0.3;
      staff.rotation.y = -0.4;
      staff.castShadow = true;
      player.add(staff);

      // Staff crystal
      const staffCrystal = new THREE.Mesh(
        new THREE.OctahedronGeometry(0.09),
        new THREE.MeshPhongMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 0.5 })
      );
      staffCrystal.position.set(0.42, 1.45, 0.35);
      staffCrystal.castShadow = true;
      player.add(staffCrystal);

      // Simple shadow plane for better shadows
      const shadowMat = new THREE.ShadowMaterial({ opacity: 0.35 });
      shadowPlane = new THREE.Mesh(new THREE.CircleGeometry(0.5, 8), shadowMat);
      shadowPlane.rotation.x = -Math.PI / 2;
      shadowPlane.position.y = -0.45;
      player.add(shadowPlane);

      playerMesh = player;
      scene.add(player);
    }

    // ============ FLOATING ISLANDS ============
    function createIsland(x, z, size, height, color) {
      const island = new THREE.Group();

      // Main grass platform
      const platform = new THREE.Mesh(
        new THREE.CylinderGeometry(size, size * 0.75, height, 12),
        new THREE.MeshLambertMaterial({ color: color })
      );
      platform.position.y = height / 2;
      platform.castShadow = true;
      platform.receiveShadow = true;
      island.add(platform);

      // Grass top edge (darker trim)
      const edge = new THREE.Mesh(
        new THREE.CylinderGeometry(size + 0.08, size + 0.08, 0.15, 12),
        new THREE.MeshLambertMaterial({ color: color })
      );
      edge.position.y = height + 0.05;
      edge.receiveShadow = true;
      island.add(edge);

      // Underground/dirt base
      const base = new THREE.Mesh(
        new THREE.CylinderGeometry(size * 0.85, size * 0.9, 2, 12),
        new THREE.MeshLambertMaterial({ color: 0x8d6e63 })
      );
      base.position.y = -1;
      island.add(base);

      island.position.set(x, 0, z);
      island.scale.set(1, 0.5 + Math.random() * 0.5, 1);
      island.userData = { height: height, center: { x, z }, radius: size };
      islandMeshes.push(island);
      scene.add(island);

      return island;
    }

    function generateArchipelago() {
      const primaryColors = [0x88d8b0, 0x96ceb4, 0xd5e8d4, 0xf5e6c3, 0xeac4d5, 0xc4c3e7];
      const islands = [];

      // Center island (spawn)
      islands.push(createIsland(0, 0, 7, 3, primaryColors[0]));

      // Secondary islands
      for (let i = 0; i < 14; i++) {
        const angle = Math.random() * Math.PI * 2;
        const dist = randomInRange(20, 45);
        const x = Math.cos(angle) * dist;
        const z = Math.sin(angle) * dist;
        const size = randomInRange(4, 8);
        const height = randomInRange(2, 5);
        islands.push(createIsland(x, z, size, height, primaryColors[Math.floor(Math.random() * primaryColors.length)]));
      }
    }

    function createBridge(start, end, segments) {
      const bridgeGroup = new THREE.Group();
      for (let i = 0; i <= segments; i++) {
        const t = i / segments;
        const x = lerp(start.x, end.x, t);
        const z = lerp(start.z, end.z, t);
        const y = lerp(start.y, end.y, t);
        const platform = createIsland(x, z, 2.2, 0.6, 0xd4a574);
        platform.position.y = y;
        bridgeGroup.add(platform);
      }
      // Railings
      bridgeGroup.userData.rails = true;
      return bridgeGroup;
    }

    // ============ DECORATIONS (Trees, Rocks) ============
    function createTree(x, z) {
      const tree = new THREE.Group();
      const trunk = new THREE.Mesh(
        new THREE.CylinderGeometry(0.2, 0.3, 1.2, 6),
        new THREE.MeshLambertMaterial({ color: 0x8d5524 })
      );
      trunk.position.y = 0.6;
      trunk.castShadow = true;
      tree.add(trunk);

      const foliage = new THREE.Mesh(
        new THREE.ConeGeometry(1.1, 2.2, 8),
        new THREE.MeshLambertMaterial({ color: 0x6ab04c })
      );
      foliage.position.y = 2;
      foliage.castShadow = true;
      tree.add(foliage);

      const foliage2 = new THREE.Mesh(
        new THREE.ConeGeometry(0.8, 1.6, 8),
        new THREE.MeshLambertMaterial({ color: 0x7fbf7a })
      );
      foliage2.position.y = 3.2;
      foliage2.castShadow = true;
      tree.add(foliage2);

      tree.position.set(x, 0, z);
      tree.userData = { type: 'tree', height: 3.2 };
      treeMeshes.push(tree);
      scene.add(tree);
    }

    function createRock(x, y, z, scale) {
      const rock = new THREE.Mesh(
        new THREE.DodecahedronGeometry(scale),
        new THREE.MeshLambertMaterial({ color: 0x9aa5b1 })
      );
      rock.position.set(x, y + scale/2, z);
      rock.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
      rock.castShadow = true;
      rock.receiveShadow = true;
      rockMeshes.push(rock);
      scene.add(rock);
    }

    function createCloud(x, y, z, scale) {
      const cloud = new THREE.Group();
      const puff1 = new THREE.Mesh(
        new THREE.SphereGeometry(2.5, 8, 8),
        new THREE.MeshLambertMaterial({ color: 0xffffff })
      );
      const puff2 = new THREE.Mesh(
        new THREE.SphereGeometry(3, 8, 8),
        new THREE.MeshLambertMaterial({ color: 0xffffff })
      );
      const puff3 = new THREE.Mesh(
        new THREE.SphereGeometry(2.2, 8, 8),
        new THREE.MeshLambertMaterial({ color: 0xffffff })
      );
      puff1.position.z = -2;
      puff2.position.x = 2;
      puff3.position.x = 4;
      cloud.add(puff1, puff2, puff3);
      cloud.position.set(x, y, z);
      cloud.scale.set(scale, scale, scale);
      cloudMeshes.push(cloud);
      scene.add(cloud);
    }

    // ============ CRYSTALS ============
    function createCrystal(x, y, z) {
      const crystal = new THREE.Mesh(
        new THREE.OctahedronGeometry(0.55, 0),
        new THREE.MeshPhongMaterial({
          color: 0x00ffff,
          emissive: 0x00ffff,
          emissiveIntensity: 0.6,
          transparent: true,
          opacity: 0.9
        })
      );
      crystal.position.set(x, y, z);
      crystal.userData = {
        bobOffset: Math.random() * Math.PI * 2,
        collected: false
      };
      crystalMeshes.push(crystal);
      scene.add(crystal);
      return crystal;
    }

    // ============ TURRETS ============
    function createTurret(x, z) {
      const turretGroup = new THREE.Group();

      const base = new THREE.Mesh(
        new THREE.CylinderGeometry(0.9, 1.1, 0.5, 10),
        new THREE.MeshLambertMaterial({ color: 0x5f27cd })
      );
      base.position.y = 0.25;
      base.castShadow = true;
      base.receiveShadow = true;
      turretGroup.add(base);

      const dome = new THREE.Mesh(
        new THREE.SphereGeometry(0.7, 10, 8, 0, Math.PI * 2, 0, Math.PI / 2),
        new THREE.MeshLambertMaterial({ color: 0x9c88ff })
      );
      dome.position.y = 0.75;
      dome.castShadow = true;
      turretGroup.add(dome);

      const barrel = new THREE.Mesh(
        new THREE.CylinderGeometry(0.12, 0.12, 1.4, 8),
        new THREE.MeshLambertMaterial({ color: 0x2d3436 })
      );
      barrel.rotation.x = Math.PI / 2;
      turretGroup.add(barrel);

      const eye = new THREE.Mesh(
        new THREE.SphereGeometry(0.18),
        new THREE.MeshPhongMaterial({ color: 0xff4757, emissive: 0xff4757, emissiveIntensity: 0.8 })
      );
      turretGroup.add(eye);

      turretGroup.position.set(x, 0, z);
      turretGroup.userData = {
        angle: 0,
        fireTimer: Math.random() * 3,
        eye: eye,
        barrel: barrel
      };
      turrets.push(turretGroup);
      scene.add(turretGroup);
      return turretGroup;
    }

    // ============ PROJECTILES ============
    function spawnProjectile(origin, target) {
      const dir = new THREE.Vector3().subVectors(target, origin).normalize();
      const color = 0xff6b6b;
      const projectile = new THREE.Mesh(
        new THREE.OctahedronGeometry(0.22),
        new THREE.MeshPhongMaterial({ color: color, emissive: color, emissiveIntensity: 0.6 })
      );
      projectile.position.copy(origin);
      projectile.userData = { velocity: dir.multiplyScalar(CONFIG.projectileSpeed), life: 4 };
      projectiles.push(projectile);
      scene.add(projectile);
    }

    // ============ PARTICLES ============
    function createParticle(x, y, z) {
      const particle = new THREE.Mesh(
        new THREE.OctahedronGeometry(0.08 + Math.random() * 0.12),
        new THREE.MeshBasicMaterial({
          color: Math.random() > 0.5 ? 0x00ffff : 0xff69b4,
          transparent: true,
          opacity: 0.6
        })
      );
      particle.position.set(
        x + randomInRange(-1, 1),
        y + randomInRange(0, 4),
        z + randomInRange(-1, 1)
      );
      particle.userData = {
        velocity: new THREE.Vector3(
          randomInRange(-0.5, 0.5),
          randomInRange(0.2, 1),
          randomInRange(-0.5, 0.5)
        ),
        life: 2 + Math.random() * 2,
        maxLife: 4
      };
      particles.push(particle);
      scene.add(particle);
    }

    // ============ INPUT HANDLING ============
    function setupInputs() {
      window.addEventListener('keydown', (e) => {
        keys[e.code] = true;
        if (e.code === 'Space') {
          e.preventDefault();
        }
      });
      window.addEventListener('keyup', (e) => keys[e.code] = false);

      document.addEventListener('mousedown', (e) => {
        if (e.target === renderer.domElement && !state.isPaused) {
          mouse.isDown = true;
          mouse.lastX = e.clientX;
          mouse.lastY = e.clientY;
        }
      });
      document.addEventListener('mousemove', (e) => {
        if (mouse.isDown && !state.isPaused) {
          const dx = e.clientX - mouse.lastX;
          const dy = e.clientY - mouse.lastY;
          cameraAngle.yaw -= dx * 0.006;
          cameraAngle.pitch -= dy * 0.006;
          cameraAngle.pitch = clamp(cameraAngle.pitch, 0.1, Math.PI / 2 - 0.05);
          mouse.lastX = e.clientX;
          mouse.lastY = e.clientY;
        }
      });
      document.addEventListener('mouseup', () => mouse.isDown = false);
      document.addEventListener('wheel', (e) => {
        if (!state.isPaused) {
          cameraOffset.z += e.deltaY * 0.03;
          cameraOffset.z = clamp(cameraOffset.z, -CONFIG.cameraMinDistance, -CONFIG.cameraMaxDistance);
        }
      });

      document.getElementById('start-btn').addEventListener('click', startGame);
      document.getElementById('restart-btn').addEventListener('click', restartGame);
      window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      });
    }

    // ============ PLAYER PHYSICS ============
    function onGround(playerPos, groundY) {
      const rayOrigin = new THREE.Vector3(playerPos.x, playerPos.y + 0.7, playerPos.z);
      const rayDirection = new THREE.Vector3(0, -1, 0);
      const raycaster = new THREE.Raycaster(rayOrigin, rayDirection, 0, 1.2);
      const intersects = raycaster.intersectObjects(scene.children, true);
      if (intersects.length > 0) {
        groundY[0] = intersects[0].point.y + 0.35;
        return true;
      }
      return false;
    }

    function updatePlayer(dt) {
      if (state.isPaused || state.gameOver) return;

      const speed = CONFIG.playerSpeed;
      const dx = { x: 0, z: 0 };

      if (keys['KeyW']) dx.z -= 1;
      if (keys['KeyS']) dx.z += 1;
      if (keys['KeyA']) dx.x -= 1;
      if (keys['KeyD']) dx.x += 1;

      if (dx.x !== 0 || dx.z !== 0) {
        const angle = cameraAngle.yaw;
        dx.x = Math.sin(angle) * dx.x + Math.cos(angle) * dx.z;
        dx.z = Math.cos(angle) * dx.x - Math.sin(angle) * dx.z; // corrected below
      }

      // Simplified forward/right relative to camera
      const forward = new THREE.Vector3(Math.sin(cameraAngle.yaw), 0, Math.cos(cameraAngle.yaw));
      const right = new THREE.Vector3(Math.sin(cameraAngle.yaw - Math.PI/2), 0, Math.cos(cameraAngle.yaw - Math.PI/2));
      const moveDir = new THREE.Vector3();
      if (keys['KeyW']) moveDir.add(forward);
      if (keys['KeyS']) moveDir.sub(forward);
      if (keys['KeyA']) moveDir.sub(right);
      if (keys['KeyD']) moveDir.add(right);
      if (moveDir.length() > 0) moveDir.normalize().multiplyScalar(speed);

      player.position.x += moveDir.x * dt;
      player.position.z += moveDir.z * dt;

      // Rotation towards movement direction
      if (moveDir.length() > 0.1) {
        player.rotation.y = Math.atan2(moveDir.x, moveDir.z);
      }

      // Jump
      if (keys['Space'] && !keys['SpaceLocked']) {
        const groundY = [0];
        if (onGround(player.position, groundY) && player.position.y <= groundY[0] + 0.1) {
          playerVelocity.y = CONFIG.jumpForce;
          keys['SpaceLocked'] = true;
          playSound('jump');
        }
      }
      if (!keys['Space']) keys['SpaceLocked'] = false;

      // Gravity
      playerVelocity.y -= CONFIG.gravity * dt;
      player.position.y += playerVelocity.y * dt;

      // Ground collision
      const groundY = [0];
      if (onGround(player.position, groundY)) {
        if (player.position.y < groundY[0]) {
          player.position.y = groundY[0];
          playerVelocity.y = 0;
        }
      }

      // Fall off world
      if (player.position.y < -30) {
        takeDamage(100);
      }

      // Keep within map bounds
      player.position.x = clamp(player.position.x, -90, 90);
      player.position.z = clamp(player.position.z, -90, 90);

      // Staff crystal animation
      const staffCrystal = player.children.find(c => c.geometry && c.geometry.type === 'OctahedronGeometry');
      if (staffCrystal) {
        staffCrystal.position.y = 1.45 + Math.sin(clock.elapsedTime * 5) * 0.05;
        staffCrystal.material.emissiveIntensity = 0.5 + Math.sin(clock.elapsedTime * 5) * 0.2;
      }
    }

    let playerVelocity = new THREE.Vector3(0, 0, 0);

    // ============ CAMERA ============
    function updateCamera(dt) {
      if (!playerMesh) return;

      cameraTarget.copy(player.position);
      const offsetX = cameraOffset.z * Math.sin(cameraAngle.yaw);
      const offsetZ = cameraOffset.z * Math.cos(cameraAngle.yaw);
      const offsetY = Math.abs(cameraOffset.z) * Math.tan(cameraAngle.pitch);

      camera.position.lerp(
        new THREE.Vector3(player.position.x + offsetX, player.position.y + offsetY + 3, player.position.z + offsetZ),
        cameraSmoothFactor
      );
      camera.lookAt(player.position.x, player.position.y + 1.5, player.position.z);
    }

    // ============ COLLISIONS ============
    function checkCollisions(dt) {
      if (state.isPaused || state.gameOver) return;

      const playerBox = new THREE.Box3().setFromObject(playerMesh);

      // Crystals
      crystalMeshes.forEach((crystal, index) => {
        if (crystal.userData.collected) return;
        const crystalBox = new THREE.Box3().setFromObject(crystal);
        if (playerBox.intersectsBox(crystalBox)) {
          crystal.userData.collected = true;
          crystal.visible = false;
          state.crystalsCollected++;
          state.score += CONFIG.crystalPoints;
          playSound('collect');
          spawnCollectEffect(crystal.position);
          updateHUD();
          if (state.crystalsCollected >= state.crystalsTotal) {
            winGame();
          }
        }
      });

      // Turrets
      turrets.forEach(turret => {
        const turretBox = new THREE.Box3().setFromObject(turret);
        const turretPos = turret.position;
        const distToPlayer = distance(turretPos, player.position);

        if (distToPlayer < CONFIG.turretRange) {
          // Rotate turret toward player
          const angleToPlayer = Math.atan2(player.position.x - turretPos.x, player.position.z - turretPos.z);
          turret.userData.angle = THREE.MathUtils.lerp(
            turret.userData.angle,
            angleToPlayer,
            dt * 2
          );
          turret.rotation.y = turret.userData.angle;
          turret.userData.barrel.rotation.x = turret.userData.angle - Math.PI / 2;
          turret.userData.barrel.rotation.z = Math.sin(clock.elapsedTime * 3) * 0.05;
          turret.userData.eye.rotation.z = Math.sin(clock.elapsedTime * 4 + 1) * 0.1;

          // Eye color pulses when targeting
          const pulse = 0.5 + Math.sin(clock.elapsedTime * 8) * 0.3;
          turret.userData.eye.material.emissiveIntensity = 0.8 + pulse * 0.3;

          // Fire
          turret.userData.fireTimer -= dt;
          if (turret.userData.fireTimer <= 0) {
            turret.userData.fireTimer = CONFIG.turretFireRate;
            playSound('shoot');
            spawnProjectile(
              new THREE.Vector3(turretPos.x, turretPos.y + 1.1, turretPos.z),
              player.position
            );
          }
        }

        if (playerBox.intersectsBox(turretBox)) {
          takeDamage(0.5);
        }
      });

      // Projectiles
      for (let i = projectiles.length - 1; i >= 0; i--) {
        const p = projectiles[i];
        const box = new THREE.Box3().setFromObject(p);
        if (playerBox.intersectsBox(box)) {
          takeDamage(15);
          projectiles.splice(i, 1);
          scene.remove(p);
          continue;
        }

        p.userData.life -= dt;
        p.position.addScaledVector(p.userData.velocity, dt);
        if (p.userData.life <= 0 || p.position.distanceTo(player.position) < 120) {
          scene.remove(p);
          projectiles.splice(i, 1);
        }
      }
    }

    function takeDamage(amount) {
      if (state.gameOver) return;
      state.health = Math.max(0, state.health - amount);
      updateHUD();
      playSound('hit');
      createDamageEffect(player.position);

      if (state.health <= 0) {
        gameOver();
      }
    }

    // ============ EFFECTS ============
    function spawnCollectEffect(pos) {
      for (let i = 0; i < 12; i++) {
        createParticle(pos.x, pos.y, pos.z);
      }
    }

    function createDamageEffect(pos) {
      for (let i = 0; i < 6; i++) {
        const particle = new THREE.Mesh(
          new THREE.SphereGeometry(0.15),
          new THREE.MeshBasicMaterial({ color: 0xff4757, transparent: true })
        );
        particle.position.copy(pos);
        particle.userData = {
          velocity: new THREE.Vector3(
            randomInRange(-3, 3),
            randomInRange(1, 4),
            randomInRange(-3, 3)
          ),
          life: 0.8
        };
        particles.push(particle);
        scene.add(particle);
      }
    }

    // ============ DAY/NIGHT CYCLE ============
    function updateDayNight(dt) {
      lightTimer += dt;
      const progress = (Math.sin(lightTimer * (2 * Math.PI / CONFIG.dayLength)) + 1) / 2;
      const dayColor = new THREE.Color(0x87CEEB);
      const nightColor = new THREE.Color(0x1a1a3e);
      const fogColor = dayColor.clone().lerp(nightColor, progress);
      const skyColor = new THREE.Color(0xffa07a).lerp(new THREE.Color(0x2c3e50), progress);

      scene.background = skyColor;
      scene.fog.color = fogColor;
      scene.fog.near = 40 + progress * 20;

      const ambStrength = lerp(0.45, 0.12, progress);
      ambientLight.intensity = ambStrength;

      const dirColor = new THREE.Color(0xffeeb1).lerp(new THREE.Color(0x5b7f99), progress);
      directionalLight.color = dirColor;
      directionalLight.intensity = lerp(1, 0.3, progress);

      const hemiTop = new THREE.Color(0xd6c0ff).lerp(new THREE.Color(0x16213e), progress);
      hemiLight.color = hemiTop;
      hemiLight.groundColor = new THREE.Color(0x1a1a3e).lerp(new THREE.Color(0x4a5a4a), progress);
    }

    // ============ UPDATE ENTITIES ============
    function updateEntities(dt) {
      // Crystal bobbing + rotation
      crystalMeshes.forEach(crystal => {
        if (crystal.userData.collected) return;
        const t = clock.elapsedTime * 2 + crystal.userData.bobOffset;
        crystal.position.y = crystal.userData.baseY + Math.sin(t) * 0.15;
        crystal.rotation.y += dt * 1.2;
        crystal.rotation.x += dt * 0.6;
        crystal.scale.setScalar(1 + Math.sin(t * 3) * 0.08);
      });

      // Clouds
      cloudMeshes.forEach(cloud => {
        cloud.position.x += dt * 0.8;
        if (cloud.position.x > 100) cloud.position.x = -100;
      });

      // Turret barrels animation
      turrets.forEach(turret => {
        turret.rotation.y += dt * 0.3;
      });

      // Particles
      for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.userData.life -= dt;
        p.userData.velocity.y -= 10 * dt;
        p.position.addScaledVector(p.userData.velocity, dt);
        p.rotation.x += dt * 3;
        if (p.userData.life <= 0) {
          scene.remove(p);
          particles.splice(i, 1);
        }
      }

      // Ambient magic particles
      if (Math.random() < 0.15) {
        createParticle(
          player.position.x + randomInRange(-15, 15),
          player.position.y + 2,
          player.position.z + randomInRange(-15, 15)
        );
      }
    }

    // ============ HUD ============
    function updateHUD() {
      document.getElementById('score').textContent = state.score;
      document.getElementById('crystals').textContent = `${state.crystalsCollected} / ${state.crystalsTotal}`;
      document.getElementById('health-bar').style.width = `${state.health}%`;
    }

    function showGameScreen(show) {
      if (show) {
        document.getElementById('game-over').classList.remove('hidden');
        document.getElementById('final-score').textContent = state.score;
        document.getElementById('final-crystals').textContent = `${state.crystalsCollected} / ${state.crystalsTotal}`;
        const minutes = Math.floor(state.elapsedTime / 60);
        const seconds = Math.floor(state.elapsedTime % 60);
        document.getElementById('final-time').textContent =
          `${minutes}m ${seconds}s`;
      } else {
        document.getElementById('game-over').classList.add('hidden');
      }
    }

    // ============ GAME CONTROL ============
    function startGame() {
      initAudio();
      state.isPaused = false;
      document.getElementById('start-screen').style.display = 'none';
      state.startTime = performance.now();
      playSound('win');
    }

    function gameOver() {
      state.gameOver = true;
      state.elapsedTime = (performance.now() - state.startTime) / 1000;
      state.isPaused = true;
      playSound('gameover');
      showGameScreen(true);
    }

    function winGame() {
      state.won = true;
      state.gameOver = true;
      state.elapsedTime = (performance.now() - state.startTime) / 1000;
      state.isPaused = true;
      playSound('win');
      const go = document.getElementById('game-over');
      const title = go.querySelector('h1');
      title.textContent = 'VICTORY!';
      showGameScreen(true);
    }

    function restartGame() {
      document.getElementById('game-over').classList.add('hidden');
      document.getElementById('start-screen').style.display = 'flex';
    }

    function resetGame() {
      state.score = 0;
      state.health = 100;
      state.crystalsCollected = 0;
      state.crystalsTotal = CONFIG.crystalTotal;
      state.gameOver = false;
      state.won = false;
      state.startTime = performance.now();
      state.elapsedTime = 0;
      state.isPaused = true;
      player.position.set(0, 5, 0);
      playerVelocity.set(0, 0, 0);
      player.rotation.set(0, 0, 0);
      cameraAngle.yaw = Math.PI;
      cameraAngle.pitch = 0.3;
      cameraOffset.z = -CONFIG.cameraDistance;

      // Clear entities
      crystalMeshes.forEach(c => scene.remove(c));
      turrets.forEach(t => scene.remove(t));
      projectiles.forEach(p => scene.remove(p));
      particles.forEach(p => scene.remove(p));
      crystalMeshes = [];
      turrets = [];
      projectiles = [];
      particles = [];

      // Place crystals
      for (let i = 0; i < state.crystalsTotal; i++) {
        const island = islandMeshes[1 + Math.floor(Math.random() * (islandMeshes.length - 1))];
        const angle = Math.random() * Math.PI * 2;
        const radius = randomInRange(0.5, island.userData.radius - 1);
        createCrystal(
          island.userData.center.x + Math.cos(angle) * radius,
          island.userData.height + 1.2,
          island.userData.center.z + Math.sin(angle) * radius
        ).userData.baseY = island.userData.height + 1.2;
      }

      // Place turrets on random islands (not spawn)
      for (let i = 0; i < CONFIG.turretCount; i++) {
        const island = islandMeshes[1 + Math.floor(Math.random() * (islandMeshes.length - 1))];
        const angle = Math.random() * Math.PI * 2;
        const radius = randomInRange(0.5, island.userData.radius - 1.2);
        createTurret(
          island.userData.center.x + Math.cos(angle) * radius,
          island.userData.center.z + Math.sin(angle) * radius
        );
      }

      updateHUD();
    }

    // ============ MAIN LOOP ============
    function animate() {
      requestAnimationFrame(animate);
      const dt = clamp(clock.getDelta(), 0, 0.1);

      updatePlayer(dt);
      updateCamera(dt);
      checkCollisions(dt);
      updateDayNight(dt);
      updateEntities(dt);

      renderer.render(scene, camera);
    }

    // ============ INITIALIZE ============
    (function init() {
      initScene();
      createPlayer();
      generateArchipelago();

      // Trees and rocks on each island
      islandMeshes.forEach((island, i) => {
        const nTrees = i === 0 ? 4 : 2;
        const nRocks = i === 0 ? 6 : 3;
        for (let j = 0; j < nTrees; j++) {
          const angle = Math.random() * Math.PI * 2;
          const radius = randomInRange(0.5, island.userData.radius - 0.8);
          createTree(island.userData.center.x + Math.cos(angle) * radius, island.userData.center.z + Math.sin(angle) * radius);
        }
        for (let j = 0; j < nRocks; j++) {
          const angle = Math.random() * Math.PI * 2;
          const radius = randomInRange(0.3, island.userData.radius - 0.8);
          createRock(island.userData.center.x + Math.cos(angle) * radius, island.userData.height, island.userData.center.z + Math.sin(angle) * radius, randomInRange(0.5, 1.2));
        }
      });

      // Clouds
      for (let i = 0; i < CONFIG.cloudCount; i++) {
        createCloud(randomInRange(-90, 90), randomInRange(25, 45), randomInRange(-90, 90), randomInRange(1.5, 3));
      }

      setupInputs();
      resetGame();
      animate();
    })();
  </script>
</body>
</html>

Result

Apodex delivered a useful visual prototype set, but the first globe dependency failure and the game restart defect prevent calling it a clean three-task success. All embedded code is preserved without manual repairs. The completed game retry’s UI reported $0, an estimated 14,628 tokens, and 62.2 seconds. The model’s ability to generate a substantial scene is visible; robust gameplay still needs verification and repair.