← Canyon Run results

Canyon Run

seed 2.0 code · full

Initial view of Canyon Run

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 54,093 bytes · SHA-256 e5ba2e100106
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>High-Speed Procedural Canyon Racer</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            overflow: hidden;
            background: #0a0a1a;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        #game-container {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        
        #hud {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 100;
        }
        
        .hud-element {
            position: absolute;
            pointer-events: none;
        }
        
        #score-container {
            top: 30px;
            left: 50%;
            transform: translateX(-50%);
            text-align: center;
        }
        
        #score {
            font-size: 48px;
            font-weight: 100;
            color: #fff;
            text-shadow: 0 0 20px rgba(255, 150, 50, 0.8);
            letter-spacing: 4px;
        }
        
        #multiplier {
            font-size: 24px;
            font-weight: 300;
            color: #ff9632;
            text-shadow: 0 0 15px rgba(255, 150, 50, 0.6);
            margin-top: 5px;
            transition: transform 0.1s, color 0.1s;
        }
        
        #multiplier.pulse {
            transform: scale(1.5);
            color: #fff;
        }
        
        #speed-container {
            bottom: 80px;
            left: 50%;
            transform: translateX(-50%);
            width: 300px;
            text-align: center;
        }
        
        #speed-bar {
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 2px;
            overflow: hidden;
        }
        
        #speed-fill {
            height: 100%;
            background: linear-gradient(90deg, #ff9632, #ff5050);
            width: 0%;
            transition: width 0.1s;
            box-shadow: 0 0 10px rgba(255, 150, 50, 0.8);
        }
        
        #speed-label {
            font-size: 14px;
            color: rgba(255, 255, 255, 0.6);
            margin-top: 8px;
            letter-spacing: 2px;
        }
        
        #energy-container {
            bottom: 30px;
            left: 50%;
            transform: translateX(-50%);
            width: 400px;
            text-align: center;
        }
        
        #energy-bar {
            width: 100%;
            height: 6px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 3px;
            overflow: hidden;
        }
        
        #energy-fill {
            height: 100%;
            background: linear-gradient(90deg, #00ffaa, #00aaff);
            width: 100%;
            transition: width 0.1s;
            box-shadow: 0 0 15px rgba(0, 255, 170, 0.8);
        }
        
        #turbo-container {
            right: 30px;
            bottom: 50%;
            transform: translateY(50%);
            width: 8px;
            height: 200px;
        }
        
        #turbo-bar {
            width: 100%;
            height: 100%;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 4px;
            overflow: hidden;
        }
        
        #turbo-fill {
            position: absolute;
            bottom: 0;
            width: 100%;
            height: 0%;
            background: linear-gradient(180deg, #ff3366, #ff9632);
            transition: height 0.1s;
            box-shadow: 0 0 15px rgba(255, 51, 102, 0.8);
        }
        
        #turbo-label {
            position: absolute;
            right: 20px;
            top: 50%;
            transform: translateY(50%) rotate(-90deg);
            transform-origin: right center;
            font-size: 12px;
            color: rgba(255, 255, 255, 0.5);
            letter-spacing: 2px;
            white-space: nowrap;
        }
        
        #controls-hint {
            position: absolute;
            bottom: 120px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 12px;
            color: rgba(255, 255, 255, 0.4);
            letter-spacing: 2px;
            text-align: center;
        }
        
        #start-screen, #game-over-screen {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(10, 10, 26, 0.95);
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            z-index: 200;
            pointer-events: auto;
        }
        
        #game-over-screen {
            display: none;
        }
        
        .title {
            font-size: 64px;
            font-weight: 100;
            color: #fff;
            text-shadow: 0 0 40px rgba(255, 150, 50, 0.8);
            letter-spacing: 8px;
            margin-bottom: 20px;
        }
        
        .subtitle {
            font-size: 18px;
            color: rgba(255, 255, 255, 0.6);
            letter-spacing: 4px;
            margin-bottom: 60px;
        }
        
        .start-btn {
            padding: 20px 60px;
            font-size: 18px;
            font-weight: 300;
            letter-spacing: 4px;
            color: #fff;
            background: linear-gradient(135deg, rgba(255, 150, 50, 0.3), rgba(255, 51, 102, 0.3));
            border: 1px solid rgba(255, 150, 50, 0.5);
            cursor: pointer;
            transition: all 0.3s;
            pointer-events: auto;
        }
        
        .start-btn:hover {
            background: linear-gradient(135deg, rgba(255, 150, 50, 0.5), rgba(255, 51, 102, 0.5));
            box-shadow: 0 0 30px rgba(255, 150, 50, 0.5);
            transform: scale(1.05);
        }
        
        .stats {
            margin: 40px 0;
            text-align: center;
        }
        
        .stat-row {
            display: flex;
            justify-content: space-between;
            width: 400px;
            padding: 15px 0;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
        }
        
        .stat-label {
            color: rgba(255, 255, 255, 0.5);
            letter-spacing: 2px;
            font-size: 14px;
        }
        
        .stat-value {
            color: #ff9632;
            font-size: 24px;
            font-weight: 200;
            text-shadow: 0 0 15px rgba(255, 150, 50, 0.5);
        }
        
        .instructions {
            margin-top: 40px;
            text-align: center;
            color: rgba(255, 255, 255, 0.4);
            font-size: 14px;
            line-height: 2;
            letter-spacing: 1px;
        }
    </style>
</head>
<body>
    <div id="game-container"></div>
    
    <div id="hud">
        <div id="score-container" class="hud-element">
            <div id="score">0</div>
            <div id="multiplier">x1.0</div>
        </div>
        
        <div id="speed-container" class="hud-element">
            <div id="speed-bar">
                <div id="speed-fill"></div>
            </div>
            <div id="speed-label">SPEED</div>
        </div>
        
        <div id="energy-container" class="hud-element">
            <div id="energy-bar">
                <div id="energy-fill"></div>
            </div>
        </div>
        
        <div id="turbo-container" class="hud-element">
            <div id="turbo-label">TURBO</div>
            <div id="turbo-bar">
                <div id="turbo-fill"></div>
            </div>
        </div>
        
        <div id="controls-hint">
            WASD / ARROWS TO STEER • SPACE TO BOOST
        </div>
    </div>
    
    <div id="start-screen">
        <div class="title">CANYON RACER</div>
        <div class="subtitle">FLY FAST. DARE CLOSE. STAY ALIVE.</div>
        <button class="start-btn" id="start-btn">START RACE</button>
        <div class="instructions">
            <div>W/↑ - PITCH DOWN (FASTER)</div>
            <div>S/↓ - PITCH UP (SLOWER)</div>
            <div>A/← D/→ - BANK LEFT/RIGHT</div>
            <div>SPACE - TURBO BOOST</div>
            <div style="margin-top: 20px; color: #00ffaa;">COLLECT ORBS TO RECHARGE ENERGY</div>
            <div style="color: #ff9632;">FLY CLOSE TO WALLS FOR MULTIPLIER</div>
        </div>
    </div>
    
    <div id="game-over-screen">
        <div class="title">GAME OVER</div>
        <div class="stats">
            <div class="stat-row">
                <span class="stat-label">FINAL SCORE</span>
                <span class="stat-value" id="final-score">0</span>
            </div>
            <div class="stat-row">
                <span class="stat-label">MAX SPEED</span>
                <span class="stat-value" id="final-speed">0</span>
            </div>
            <div class="stat-row">
                <span class="stat-label">MAX MULTIPLIER</span>
                <span class="stat-value" id="final-multiplier">x1.0</span>
            </div>
        </div>
        <button class="start-btn" id="restart-btn">RACE AGAIN</button>
    </div>

    <script>
        // ============== GAME STATE ==============
        const GameState = {
            MENU: 'menu',
            PLAYING: 'playing',
            GAMEOVER: 'gameover'
        };
        
        let currentState = GameState.MENU;
        let score = 0;
        let maxSpeed = 0;
        let maxMultiplier = 1.0;
        
        // ============== INPUT HANDLER ==============
        const Input = {
            pitchUp: false,
            pitchDown: false,
            bankLeft: false,
            bankRight: false,
            boost: false
        };
        
        document.addEventListener('keydown', (e) => {
            switch(e.code) {
                case 'KeyW': case 'ArrowUp': Input.pitchDown = true; break;
                case 'KeyS': case 'ArrowDown': Input.pitchUp = true; break;
                case 'KeyA': case 'ArrowLeft': Input.bankLeft = true; break;
                case 'KeyD': case 'ArrowRight': Input.bankRight = true; break;
                case 'Space': Input.boost = true; e.preventDefault(); break;
            }
        });
        
        document.addEventListener('keyup', (e) => {
            switch(e.code) {
                case 'KeyW': case 'ArrowUp': Input.pitchDown = false; break;
                case 'KeyS': case 'ArrowDown': Input.pitchUp = false; break;
                case 'KeyA': case 'ArrowLeft': Input.bankLeft = false; break;
                case 'KeyD': case 'ArrowRight': Input.bankRight = false; break;
                case 'Space': Input.boost = false; break;
            }
        });
        
        // ============== AUDIO SYSTEM ==============
        class AudioSystem {
            constructor() {
                this.audioContext = null;
                this.masterGain = null;
                this.engineOsc = null;
                this.engineGain = null;
            }
            
            init() {
                const AudioContext = window.AudioContext || window.webkitAudioContext;
                this.audioContext = new AudioContext();
                this.masterGain = this.audioContext.createGain();
                this.masterGain.gain.value = 0.3;
                this.masterGain.connect(this.audioContext.destination);
                
                // Engine hum
                this.engineOsc = this.audioContext.createOscillator();
                this.engineGain = this.audioContext.createGain();
                this.engineOsc.type = 'sawtooth';
                this.engineOsc.frequency.value = 60;
                this.engineGain.gain.value = 0.1;
                this.engineOsc.connect(this.engineGain);
                this.engineGain.connect(this.masterGain);
                this.engineOsc.start();
                
                // LFO for engine pulse
                this.lfo = this.audioContext.createOscillator();
                this.lfoGain = this.audioContext.createGain();
                this.lfo.frequency.value = 2;
                this.lfoGain.gain.value = 10;
                this.lfo.connect(this.lfoGain);
                this.lfoGain.connect(this.engineOsc.frequency);
                this.lfo.start();
            }
            
            update(speed, boosting) {
                if (!this.engineOsc) return;
                const baseFreq = 60 + speed * 0.5;
                this.engineOsc.frequency.setTargetAtTime(baseFreq, this.audioContext.currentTime, 0.1);
                this.engineGain.gain.setTargetAtTime(boosting ? 0.2 : 0.1, this.audioContext.currentTime, 0.1);
                this.lfo.frequency.setTargetAtTime(boosting ? 8 : 2, this.audioContext.currentTime, 0.1);
            }
            
            playCollect() {
                if (!this.audioContext) return;
                const osc = this.audioContext.createOscillator();
                const gain = this.audioContext.createGain();
                osc.type = 'sine';
                osc.frequency.setValueAtTime(880, this.audioContext.currentTime);
                osc.frequency.exponentialRampToValueAtTime(1760, this.audioContext.currentTime + 0.1);
                gain.gain.setValueAtTime(0.3, this.audioContext.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.3);
                osc.connect(gain);
                gain.connect(this.masterGain);
                osc.start();
                osc.stop(this.audioContext.currentTime + 0.3);
            }
            
            playNearMiss(intensity) {
                if (!this.audioContext) return;
                const noise = this.audioContext.createBufferSource();
                const bufferSize = this.audioContext.sampleRate * 0.2;
                const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) {
                    data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
                }
                noise.buffer = buffer;
                const filter = this.audioContext.createBiquadFilter();
                filter.type = 'bandpass';
                filter.frequency.value = 1000 + intensity * 2000;
                filter.Q.value = 1;
                const gain = this.audioContext.createGain();
                gain.gain.value = 0.15 * intensity;
                noise.connect(filter);
                filter.connect(gain);
                gain.connect(this.masterGain);
                noise.start();
            }
            
            playCrash() {
                if (!this.audioContext) return;
                const noise = this.audioContext.createBufferSource();
                const bufferSize = this.audioContext.sampleRate * 0.5;
                const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) {
                    data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 0.5);
                }
                noise.buffer = buffer;
                const filter = this.audioContext.createBiquadFilter();
                filter.type = 'lowpass';
                filter.frequency.value = 200;
                const gain = this.audioContext.createGain();
                gain.gain.value = 0.4;
                noise.connect(filter);
                filter.connect(gain);
                gain.connect(this.masterGain);
                noise.start();
            }
        }
        
        const audio = new AudioSystem();
        
        // ============== THREE.JS SETUP ==============
        const container = document.getElementById('game-container');
        const scene = new THREE.Scene();
        scene.background = new THREE.Color(0x0a0a1a);
        scene.fog = new THREE.FogExp2(0x1a0a2e, 0.003);
        
        const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 3, -8);
        
        const 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;
        container.appendChild(renderer.domElement);
        
        // Post-processing disabled for file:// loading - game uses emissive materials for glow effect
        
        // Lighting
        const ambientLight = new THREE.AmbientLight(0x4a2a6e, 0.4);
        scene.add(ambientLight);
        
        const sunLight = new THREE.DirectionalLight(0xff9966, 1.2);
        sunLight.position.set(50, 100, -50);
        sunLight.castShadow = true;
        sunLight.shadow.mapSize.width = 2048;
        sunLight.shadow.mapSize.height = 2048;
        sunLight.shadow.camera.near = 0.5;
        sunLight.shadow.camera.far = 500;
        sunLight.shadow.camera.left = -100;
        sunLight.shadow.camera.right = 100;
        sunLight.shadow.camera.top = 100;
        sunLight.shadow.camera.bottom = -100;
        scene.add(sunLight);
        
        const fillLight = new THREE.DirectionalLight(0x6644aa, 0.5);
        fillLight.position.set(-30, 20, 30);
        scene.add(fillLight);
        
        // ============== PLAYER ==============
        class Player {
            constructor() {
                this.group = new THREE.Group();
                
                // Main body - sleek arrow shape
                const bodyGeo = new THREE.ConeGeometry(0.4, 2.5, 4);
                bodyGeo.rotateX(Math.PI / 2);
                const bodyMat = new THREE.MeshStandardMaterial({
                    color: 0x4488ff,
                    metalness: 0.8,
                    roughness: 0.2,
                    flatShading: true
                });
                this.body = new THREE.Mesh(bodyGeo, bodyMat);
                this.body.castShadow = true;
                this.group.add(this.body);
                
                // Wings
                const wingGeo = new THREE.BoxGeometry(2.5, 0.05, 0.8);
                const wingMat = new THREE.MeshStandardMaterial({
                    color: 0x3366cc,
                    metalness: 0.9,
                    roughness: 0.1,
                    flatShading: true
                });
                this.wings = new THREE.Mesh(wingGeo, wingMat);
                this.wings.position.z = 0.3;
                this.wings.castShadow = true;
                this.group.add(this.wings);
                
                // Engine glow
                const engineGeo = new THREE.SphereGeometry(0.2, 8, 8);
                const engineMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa });
                this.engineGlow = new THREE.Mesh(engineGeo, engineMat);
                this.engineGlow.position.z = 1.4;
                this.group.add(this.engineGlow);
                
                // Engine point light
                this.engineLight = new THREE.PointLight(0x00ffaa, 2, 10);
                this.engineLight.position.z = 2;
                this.group.add(this.engineLight);
                
                // Physics properties
                this.position = new THREE.Vector3(0, 2, 0);
                this.velocity = new THREE.Vector3(0, 0, 0);
                this.targetBank = 0;
                this.currentBank = 0;
                this.targetPitch = 0;
                this.currentPitch = 0;
                this.baseSpeed = 0.5;
                this.speed = this.baseSpeed;
                this.maxSpeed = 3.0;
                this.energy = 100;
                this.turbo = 0;
                this.isBoosting = false;
                this.multiplier = 1.0;
                this.multiplierDecay = 0.005;
                
                // Collision
                this.boundingRadius = 0.8;
                
                scene.add(this.group);
            }
            
            reset() {
                this.position.set(0, 2, 0);
                this.velocity.set(0, 0, 0);
                this.targetBank = 0;
                this.currentBank = 0;
                this.targetPitch = 0;
                this.currentPitch = 0;
                this.speed = this.baseSpeed;
                this.energy = 100;
                this.turbo = 0;
                this.isBoosting = false;
                this.multiplier = 1.0;
            }
            
            update(deltaTime, obstacles, orbs) {
                // Input
                this.targetBank = 0;
                this.targetPitch = 0;
                
                if (Input.bankLeft) this.targetBank = 1.0;
                if (Input.bankRight) this.targetBank = -1.0;
                if (Input.pitchDown) this.targetPitch = -0.5;
                if (Input.pitchUp) this.targetPitch = 0.5;
                
                // Smooth interpolation
                this.currentBank += (this.targetBank - this.currentBank) * 0.1;
                this.currentPitch += (this.targetPitch - this.currentPitch) * 0.1;
                
                // Speed based on pitch (pitch down = faster)
                const targetSpeed = this.baseSpeed - this.currentPitch * 0.8;
                
                // Boost logic
                if (Input.boost && this.turbo > 0.1) {
                    this.isBoosting = true;
                    this.turbo -= deltaTime * 30;
                    if (this.turbo < 0) this.turbo = 0;
                } else {
                    this.isBoosting = false;
                }
                
                const boostMultiplier = this.isBoosting ? 2.0 : 1.0;
                this.speed += (targetSpeed * boostMultiplier - this.speed) * 0.05;
                
                // Clamp speed
                const actualMaxSpeed = this.maxSpeed * boostMultiplier;
                if (this.speed > actualMaxSpeed) this.speed = actualMaxSpeed;
                if (this.speed < 0.1) this.speed = 0.1;
                
                // Energy decay
                this.energy -= deltaTime * (3 + this.speed * 2);
                if (this.energy < 0) {
                    this.energy = 0;
                    return false; // Game over
                }
                
                // Build turbo when at high speed
                if (this.speed > this.baseSpeed * 1.5 && !this.isBoosting) {
                    this.turbo += deltaTime * 15;
                    if (this.turbo > 100) this.turbo = 100;
                }
                
                // Movement
                const moveSpeed = this.speed * 60 * deltaTime;
                this.velocity.z = moveSpeed;
                
                // Horizontal movement based on bank
                this.velocity.x = this.currentBank * moveSpeed * 0.8;
                
                // Vertical movement based on pitch
                this.velocity.y = -this.currentPitch * moveSpeed * 0.5;
                
                this.position.add(this.velocity);
                
                // Clamp vertical position
                if (this.position.y < -20) this.position.y = -20;
                if (this.position.y > 80) this.position.y = 80;
                
                // Multiplier decay
                this.multiplier -= this.multiplierDecay;
                if (this.multiplier < 1.0) this.multiplier = 1.0;
                
                // Update visual transform
                this.group.position.copy(this.position);
                this.group.rotation.z = this.currentBank * 0.8;
                this.group.rotation.x = this.currentPitch * 0.3;
                
                // Engine glow intensity
                const glowIntensity = 0.8 + this.speed * 0.3 + (this.isBoosting ? 1.5 : 0);
                this.engineLight.intensity = glowIntensity;
                this.engineGlow.scale.setScalar(1 + this.speed * 0.2 + (this.isBoosting ? 0.5 : 0));
                
                // Add turbo to score
                if (this.isBoosting) {
                    this.multiplier += 0.01;
                }
                
                return true;
            }
        }
        
        // ============== PARTICLE SYSTEMS ==============
        class ParticleSystem {
            constructor() {
                // Trail particles behind player
                this.trailParticles = [];
                this.trailGeo = new THREE.SphereGeometry(0.1, 4, 4);
                this.trailMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa, transparent: true });
                
                // Debris/dust particles flying past
                this.debrisCount = 200;
                const debrisGeo = new THREE.BufferGeometry();
                const positions = new Float32Array(this.debrisCount * 3);
                const colors = new Float32Array(this.debrisCount * 3);
                
                for (let i = 0; i < this.debrisCount; i++) {
                    positions[i * 3] = (Math.random() - 0.5) * 200;
                    positions[i * 3 + 1] = (Math.random() - 0.5) * 100;
                    positions[i * 3 + 2] = Math.random() * 500 - 100;
                    
                    const color = new THREE.Color();
                    color.setHSL(0.08 + Math.random() * 0.1, 0.5, 0.3 + Math.random() * 0.3);
                    colors[i * 3] = color.r;
                    colors[i * 3 + 1] = color.g;
                    colors[i * 3 + 2] = color.b;
                }
                
                debrisGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
                debrisGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
                
                const debrisMat = new THREE.PointsMaterial({
                    size: 0.3,
                    vertexColors: true,
                    transparent: true,
                    opacity: 0.6
                });
                
                this.debris = new THREE.Points(debrisGeo, debrisMat);
                scene.add(this.debris);
            }
            
            emitTrail(player) {
                if (this.trailParticles.length > 30) {
                    const p = this.trailParticles.shift();
                    scene.remove(p.mesh);
                }
                
                const mesh = new THREE.Mesh(this.trailGeo, this.trailMat.clone());
                mesh.position.copy(player.position);
                mesh.position.z += 1.5;
                mesh.position.x += (Math.random() - 0.5) * 0.5;
                mesh.position.y += (Math.random() - 0.5) * 0.3;
                
                scene.add(mesh);
                
                this.trailParticles.push({
                    mesh: mesh,
                    life: 1.0,
                    speed: player.speed
                });
            }
            
            update(deltaTime, playerSpeed, playerPos) {
                // Update trail particles
                for (let i = this.trailParticles.length - 1; i >= 0; i--) {
                    const p = this.trailParticles[i];
                    p.life -= deltaTime * 2;
                    p.mesh.position.z += p.speed * deltaTime * 30;
                    p.mesh.scale.setScalar(p.life);
                    p.mesh.material.opacity = p.life;
                    
                    if (p.life <= 0) {
                        scene.remove(p.mesh);
                        this.trailParticles.splice(i, 1);
                    }
                }
                
                // Update debris - move toward camera
                const positions = this.debris.geometry.attributes.position.array;
                for (let i = 0; i < this.debrisCount; i++) {
                    positions[i * 3 + 2] -= playerSpeed * deltaTime * 120;
                    
                    // Recycle when behind camera
                    if (positions[i * 3 + 2] < -50) {
                        positions[i * 3] = playerPos.x + (Math.random() - 0.5) * 200;
                        positions[i * 3 + 1] = playerPos.y + (Math.random() - 0.5) * 100;
                        positions[i * 3 + 2] = 400 + Math.random() * 200;
                    }
                }
                this.debris.geometry.attributes.position.needsUpdate = true;
            }
        }
        
        // ============== PROCEDURAL GENERATION ==============
        class WorldGenerator {
            constructor() {
                this.chunkSize = 100;
                this.chunks = [];
                this.obstacles = [];
                this.orbs = [];
                this.lastChunkZ = 0;
                this.difficulty = 1;
                
                // Materials
                this.wallMat = new THREE.MeshStandardMaterial({
                    color: 0x4a3a5e,
                    metalness: 0.3,
                    roughness: 0.8,
                    flatShading: true
                });
                
                this.wallHighlightMat = new THREE.MeshStandardMaterial({
                    color: 0x6a4a7e,
                    metalness: 0.4,
                    roughness: 0.7,
                    flatShading: true
                });
                
                this.monolithMat = new THREE.MeshStandardMaterial({
                    color: 0x2a2a4e,
                    metalness: 0.6,
                    roughness: 0.4,
                    flatShading: true
                });
                
                this.orbMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa });
            }
            
            reset() {
                for (const chunk of this.chunks) {
                    scene.remove(chunk.mesh);
                }
                for (const obs of this.obstacles) {
                    scene.remove(obs.mesh);
                }
                for (const orb of this.orbs) {
                    scene.remove(orb.mesh);
                    scene.remove(orb.light);
                }
                this.chunks = [];
                this.obstacles = [];
                this.orbs = [];
                this.lastChunkZ = 0;
                this.difficulty = 1;
            }
            
            generateChunk(startZ) {
                const chunk = {
                    startZ: startZ,
                    endZ: startZ + this.chunkSize,
                    mesh: new THREE.Group(),
                    obstacles: [],
                    orbs: []
                };
                
                // Canyon walls - sine wave pattern
                const segments = 10;
                const segLength = this.chunkSize / segments;
                
                for (let i = 0; i < segments; i++) {
                    const z = startZ + i * segLength;
                    const nextZ = z + segLength;
                    
                    // Vary canyon width based on difficulty and position
                    const widthOffset = Math.sin(z * 0.01 + this.difficulty) * 15 * this.difficulty;
                    const baseWidth = 40 - this.difficulty * 5;
                    const canyonWidth = Math.max(baseWidth, 20);
                    
                    // Left wall segment
                    const leftX = -canyonWidth / 2 + widthOffset;
                    this.createWallSegment(chunk.mesh, leftX, z, nextZ, true);
                    
                    // Right wall segment
                    const rightX = canyonWidth / 2 + widthOffset;
                    this.createWallSegment(chunk.mesh, rightX, z, nextZ, false);
                    
                    // Add monoliths (floating obstacles)
                    if (Math.random() < 0.3 * this.difficulty) {
                        const monoX = (Math.random() - 0.5) * (canyonWidth - 15);
                        const monoY = 5 + Math.random() * 20;
                        const monoZ = z + segLength / 2;
                        this.createMonolith(chunk.mesh, monoX, monoY, monoZ, chunk.obstacles);
                    }
                    
                    // Add tight squeeze obstacles
                    if (Math.random() < 0.2 * this.difficulty && i > 0 && i < segments - 1) {
                        this.createTightSqueeze(chunk.mesh, z + segLength / 2, canyonWidth, widthOffset, chunk.obstacles);
                    }
                    
                    // Add energy orbs
                    const orbCount = 1 + Math.floor(Math.random() * 3);
                    for (let o = 0; o < orbCount; o++) {
                        const orbX = (Math.random() - 0.5) * (canyonWidth - 10);
                        const orbY = 2 + Math.random() * 15;
                        const orbZ = z + Math.random() * segLength;
                        this.createOrb(chunk.mesh, orbX, orbY, orbZ, chunk.orbs);
                    }
                }
                
                scene.add(chunk.mesh);
                this.chunks.push(chunk);
                this.obstacles.push(...chunk.obstacles);
                this.orbs.push(...chunk.orbs);
            }
            
            createWallSegment(parent, x, startZ, endZ, isLeft) {
                const height = 30 + Math.random() * 20;
                const depth = endZ - startZ;
                const thickness = 10 + Math.random() * 10;
                
                // Main wall body
                const geo = new THREE.BoxGeometry(thickness, height, depth);
                const wall = new THREE.Mesh(geo, isLeft ? this.wallMat : this.wallHighlightMat);
                
                wall.position.set(
                    x + (isLeft ? -thickness / 2 : thickness / 2),
                    height / 2 - 10,
                    startZ + depth / 2
                );
                
                wall.castShadow = true;
                wall.receiveShadow = true;
                parent.add(wall);
                
                // Add some rock formations
                for (let i = 0; i < 3; i++) {
                    const rockGeo = new THREE.DodecahedronGeometry(2 + Math.random() * 4, 0);
                    const rock = new THREE.Mesh(rockGeo, this.wallMat);
                    rock.position.set(
                        x + (isLeft ? -1 : 1) * (2 + Math.random() * 5),
                        Math.random() * height - 5,
                        startZ + Math.random() * depth
                    );
                    rock.rotation.set(Math.random(), Math.random(), Math.random());
                    rock.castShadow = true;
                    parent.add(rock);
                }
            }
            
            createMonolith(parent, x, y, z, obstacleList) {
                const height = 15 + Math.random() * 25;
                const width = 3 + Math.random() * 4;
                
                const geo = new THREE.BoxGeometry(width, height, width);
                const monolith = new THREE.Mesh(geo, this.monolithMat);
                monolith.position.set(x, y, z);
                monolith.castShadow = true;
                monolith.receiveShadow = true;
                parent.add(monolith);
                
                // Add glow on top
                const topGeo = new THREE.OctahedronGeometry(width * 0.6, 0);
                const topMat = new THREE.MeshBasicMaterial({ color: 0xff5566 });
                const top = new THREE.Mesh(topGeo, topMat);
                top.position.set(x, y + height / 2 + 2, z);
                parent.add(top);
                
                const light = new THREE.PointLight(0xff5566, 1, 15);
                light.position.copy(top.position);
                parent.add(light);
                
                obstacleList.push({
                    mesh: monolith,
                    position: new THREE.Vector3(x, y, z),
                    radius: width * 0.7,
                    type: 'monolith'
                });
            }
            
            createTightSqueeze(parent, z, canyonWidth, offset, obstacleList) {
                const gapSize = 8 - this.difficulty;
                const blockerWidth = (canyonWidth - gapSize) / 2;
                
                // Left blocker
                const leftGeo = new THREE.BoxGeometry(blockerWidth, 20, 8);
                const left = new THREE.Mesh(leftGeo, this.monolithMat);
                left.position.set(-canyonWidth / 2 + offset + blockerWidth / 2, 5, z);
                left.castShadow = true;
                parent.add(left);
                
                // Right blocker
                const right = new THREE.Mesh(leftGeo, this.monolithMat);
                right.position.set(canyonWidth / 2 + offset - blockerWidth / 2, 5, z);
                right.castShadow = true;
                parent.add(right);
                
                obstacleList.push({
                    mesh: left,
                    position: new THREE.Vector3(left.position.x, left.position.y, z),
                    radius: blockerWidth * 0.6,
                    type: 'gap'
                });
                
                obstacleList.push({
                    mesh: right,
                    position: new THREE.Vector3(right.position.x, right.position.y, z),
                    radius: blockerWidth * 0.6,
                    type: 'gap'
                });
            }
            
            createOrb(parent, x, y, z, orbList) {
                const geo = new THREE.IcosahedronGeometry(0.8, 0);
                const orb = new THREE.Mesh(geo, this.orbMat);
                orb.position.set(x, y, z);
                parent.add(orb);
                
                const light = new THREE.PointLight(0x00ffaa, 2, 12);
                light.position.copy(orb.position);
                parent.add(light);
                
                orbList.push({
                    mesh: orb,
                    light: light,
                    position: new THREE.Vector3(x, y, z),
                    radius: 1.2,
                    collected: false,
                    rotationOffset: Math.random() * Math.PI * 2
                });
            }
            
            update(playerPos, score) {
                // Update difficulty
                this.difficulty = 1 + score / 5000;
                if (this.difficulty > 4) this.difficulty = 4;
                
                // Generate new chunks
                while (this.lastChunkZ < playerPos.z + 500) {
                    this.generateChunk(this.lastChunkZ);
                    this.lastChunkZ += this.chunkSize;
                }
                
                // Remove old chunks
                for (let i = this.chunks.length - 1; i >= 0; i--) {
                    const chunk = this.chunks[i];
                    if (chunk.endZ < playerPos.z - 100) {
                        scene.remove(chunk.mesh);
                        
                        // Remove from obstacles list
                        this.obstacles = this.obstacles.filter(o => !chunk.obstacles.includes(o));
                        this.orbs = this.orbs.filter(o => !chunk.orbs.includes(o));
                        
                        this.chunks.splice(i, 1);
                    }
                }
                
                // Animate orbs
                const time = Date.now() * 0.001;
                for (const orb of this.orbs) {
                    if (!orb.collected) {
                        orb.mesh.rotation.y = time * 2 + orb.rotationOffset;
                        orb.mesh.rotation.x = time * 1.5;
                        orb.mesh.position.y = orb.position.y + Math.sin(time * 3 + orb.rotationOffset) * 0.5;
                    }
                }
            }
            
            checkCollisions(player) {
                const playerPos = player.position;
                let hitSomething = false;
                let nearMiss = false;
                let nearMissIntensity = 0;
                
                // Check obstacles
                for (const obs of this.obstacles) {
                    const dx = playerPos.x - obs.position.x;
                    const dy = playerPos.y - obs.position.y;
                    const dz = playerPos.z - obs.position.z;
                    const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
                    
                    const minDist = player.boundingRadius + obs.radius;
                    
                    if (dist < minDist) {
                        hitSomething = true;
                    } else if (dist < minDist * 3) {
                        nearMiss = true;
                        const intensity = 1 - (dist - minDist) / (minDist * 2);
                        if (intensity > nearMissIntensity) nearMissIntensity = intensity;
                    }
                }
                
                // Check canyon walls (simple distance from center)
                // This approximates the sine-wave walls
                const canyonWidth = 35;
                const wallDistLeft = Math.abs(playerPos.x + canyonWidth / 2);
                const wallDistRight = Math.abs(playerPos.x - canyonWidth / 2);
                
                if (wallDistLeft < 5 || wallDistRight < 5) {
                    hitSomething = true;
                } else if (wallDistLeft < 15) {
                    nearMiss = true;
                    const intensity = 1 - (wallDistLeft - 5) / 10;
                    if (intensity > nearMissIntensity) nearMissIntensity = intensity;
                } else if (wallDistRight < 15) {
                    nearMiss = true;
                    const intensity = 1 - (wallDistRight - 5) / 10;
                    if (intensity > nearMissIntensity) nearMissIntensity = intensity;
                }
                
                // Check orbs
                for (const orb of this.orbs) {
                    if (orb.collected) continue;
                    
                    const dx = playerPos.x - orb.position.x;
                    const dy = playerPos.y - orb.position.y;
                    const dz = playerPos.z - orb.position.z;
                    const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
                    
                    if (dist < player.boundingRadius + orb.radius) {
                        orb.collected = true;
                        orb.mesh.visible = false;
                        orb.light.visible = false;
                        
                        // Recharge energy
                        player.energy += 25;
                        if (player.energy > 100) player.energy = 100;
                        
                        // Add to turbo
                        player.turbo += 15;
                        if (player.turbo > 100) player.turbo = 100;
                        
                        audio.playCollect();
                    }
                }
                
                return { hitSomething, nearMiss, nearMissIntensity };
            }
        }
        
        // ============== CAMERA CONTROLLER ==============
        class CameraController {
            constructor() {
                this.baseFov = 70;
                this.targetFov = this.baseFov;
                this.currentFov = this.baseFov;
                this.shakeAmount = 0;
                this.shakeDecay = 0.9;
            }
            
            update(player, deltaTime) {
                // Position behind and above player
                const targetPos = new THREE.Vector3(
                    player.position.x - player.currentBank * 2,
                    player.position.y + 3 - player.currentPitch * 2,
                    player.position.z - 10
                );
                
                camera.position.lerp(targetPos, 0.05);
                
                // Look at a point ahead of player
                const lookTarget = new THREE.Vector3(
                    player.position.x + player.currentBank * 3,
                    player.position.y - player.currentPitch * 3,
                    player.position.z + 15
                );
                camera.lookAt(lookTarget);
                
                // FOV for speed effect
                this.targetFov = this.baseFov + player.speed * 8 + (player.isBoosting ? 15 : 0);
                this.currentFov += (this.targetFov - this.currentFov) * 0.1;
                camera.fov = this.currentFov;
                camera.updateProjectionMatrix();
                
                // Camera shake
                if (this.shakeAmount > 0.001) {
                    camera.position.x += (Math.random() - 0.5) * this.shakeAmount;
                    camera.position.y += (Math.random() - 0.5) * this.shakeAmount;
                    this.shakeAmount *= this.shakeDecay;
                }
            }
            
            addShake(amount) {
                this.shakeAmount += amount;
                if (this.shakeAmount > 1) this.shakeAmount = 1;
            }
        }
        
        // ============== HUD ==============
        function updateHUD(player, currentScore) {
            document.getElementById('score').textContent = Math.floor(currentScore).toLocaleString();
            
            const multiplierEl = document.getElementById('multiplier');
            multiplierEl.textContent = `x${player.multiplier.toFixed(1)}`;
            
            const speedPercent = (player.speed / player.maxSpeed) * 100;
            document.getElementById('speed-fill').style.width = `${Math.min(speedPercent, 100)}%`;
            document.getElementById('speed-label').textContent = `SPEED: ${Math.floor(player.speed * 100)}`;
            
            document.getElementById('energy-fill').style.width = `${player.energy}%`;
            
            // Change energy color based on level
            const energyFill = document.getElementById('energy-fill');
            if (player.energy < 25) {
                energyFill.style.background = 'linear-gradient(90deg, #ff3366, #ff5050)';
            } else if (player.energy < 50) {
                energyFill.style.background = 'linear-gradient(90deg, #ff9632, #ffcc00)';
            } else {
                energyFill.style.background = 'linear-gradient(90deg, #00ffaa, #00aaff)';
            }
            
            document.getElementById('turbo-fill').style.height = `${player.turbo}%`;
        }
        
        function showGameOver(score, speed, mult) {
            document.getElementById('game-over-screen').style.display = 'flex';
            document.getElementById('final-score').textContent = Math.floor(score).toLocaleString();
            document.getElementById('final-speed').textContent = Math.floor(speed * 100);
            document.getElementById('final-multiplier').textContent = `x${mult.toFixed(1)}`;
            currentState = GameState.GAMEOVER;
        }
        
        function hideScreens() {
            document.getElementById('start-screen').style.display = 'none';
            document.getElementById('game-over-screen').style.display = 'none';
        }
        
        // ============== MAIN GAME ==============
        let player, world, particles, camController;
        
        function initGame() {
            if (player) {
                player.reset();
                world.reset();
            } else {
                player = new Player();
                world = new WorldGenerator();
                particles = new ParticleSystem();
                camController = new CameraController();
            }
            
            score = 0;
            maxSpeed = 0;
            maxMultiplier = 1.0;
            
            audio.init();
            hideScreens();
            currentState = GameState.PLAYING;
        }
        
        // Event listeners
        document.getElementById('start-btn').addEventListener('click', initGame);
        document.getElementById('restart-btn').addEventListener('click', initGame);
        
        // Background stars
        function createStars() {
            const starGeo = new THREE.BufferGeometry();
            const positions = new Float32Array(1000 * 3);
            
            for (let i = 0; i < 1000; i++) {
                positions[i * 3] = (Math.random() - 0.5) * 1000;
                positions[i * 3 + 1] = Math.random() * 200 + 50;
                positions[i * 3 + 2] = (Math.random() - 0.5) * 2000;
            }
            
            starGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
            const starMat = new THREE.PointsMaterial({
                color: 0xffffff,
                size: 0.5,
                transparent: true,
                opacity: 0.8
            });
            
            const stars = new THREE.Points(starGeo, starMat);
            scene.add(stars);
            return stars;
        }
        
        const stars = createStars();
        
        // Sun/atmosphere
        function createSun() {
            const sunGeo = new THREE.CircleGeometry(15, 32);
            const sunMat = new THREE.MeshBasicMaterial({ color: 0xff9966 });
            const sun = new THREE.Mesh(sunGeo, sunMat);
            sun.position.set(80, 80, -200);
            
            // Glow
            const glowGeo = new THREE.CircleGeometry(25, 32);
            const glowMat = new THREE.MeshBasicMaterial({ 
                color: 0xff6644,
                transparent: true,
                opacity: 0.3
            });
            const glow = new THREE.Mesh(glowGeo, glowMat);
            sun.add(glow);
            
            scene.add(sun);
            return sun;
        }
        
        const sun = createSun();
        
        // ============== GAME LOOP ==============
        let lastTime = performance.now();
        let multiplierPulseTimeout = null;
        
        function animate() {
            requestAnimationFrame(animate);
            
            const currentTime = performance.now();
            const deltaTime = (currentTime - lastTime) / 1000;
            lastTime = currentTime;
            
            // Limit delta to prevent huge jumps
            const dt = Math.min(deltaTime, 0.05);
            
            if (currentState === GameState.PLAYING) {
                // Update player
                const alive = player.update(dt, world.obstacles, world.orbs);
                
                if (!alive) {
                    audio.playCrash();
                    showGameOver(score, maxSpeed, maxMultiplier);
                    return;
                }
                
                // Update world
                world.update(player.position, score);
                
                // Collision detection
                const collision = world.checkCollisions(player);
                
                if (collision.hitSomething) {
                    audio.playCrash();
                    showGameOver(score, maxSpeed, maxMultiplier);
                    return;
                }
                
                if (collision.nearMiss) {
                    // Increase multiplier based on proximity
                    player.multiplier += collision.nearMissIntensity * 0.02;
                    camController.addShake(collision.nearMissIntensity * 0.3);
                    
                    // Visual pulse
                    const multiplierEl = document.getElementById('multiplier');
                    multiplierEl.classList.add('pulse');
                    if (multiplierPulseTimeout) clearTimeout(multiplierPulseTimeout);
                    multiplierPulseTimeout = setTimeout(() => {
                        multiplierEl.classList.remove('pulse');
                    }, 100);
                    
                    // Sound
                    if (Math.random() < 0.1) {
                        audio.playNearMiss(collision.nearMissIntensity);
                    }
                }
                
                // Track max values
                if (player.speed > maxSpeed) maxSpeed = player.speed;
                if (player.multiplier > maxMultiplier) maxMultiplier = player.multiplier;
                
                // Score
                score += player.speed * player.multiplier * dt * 50;
                
                // Particles
                if (Math.random() < 0.5) {
                    particles.emitTrail(player);
                }
                particles.update(dt, player.speed, player.position);
                
                // Camera
                camController.update(player, dt);
                
                // Audio
                audio.update(player.speed, player.isBoosting);
                
                // HUD
                updateHUD(player, score);
            }
            
            // Render
            renderer.render(scene, camera);
        }
        
        // Handle resize
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        
        // Start animation loop
        animate();
    </script>
</body>
</html>
<!-- agent-meta {"model":"bytedance-seed/seed-2.0-code","provider":"openrouter","persona":"full","sessionId":"87b519ee-9805-4888-978c-68cd981c61d1","tokensIn":776179,"tokensOut":19115,"tokensTotal":795294,"cost":0.4454345,"turns":22,"toolCalls":21,"failedToolCalls":1,"timestamp":"2026-08-15T21:40:50.689Z"} -->