← Canyon Run results

Canyon Run

Qwen 3.6 27b · typescript

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 73,129 bytes · SHA-256 9b3172190bcd
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Run — High-Speed Procedural Racer</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0514; }
#app { width: 100%; height: 100%; position: relative; }
#canvas-container { width: 100%; height: 100%; }
#canvas-container canvas { display: block; width: 100%; height: 100%; }
</style>
</head>
<body>
<div id="app"></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./state":"src/state.ts","./player":"src/player.ts","./world":"src/world.ts","./collision":"src/collision.ts","./audio":"src/audio.ts","./particles":"src/particles.ts","./hud":"src/hud.ts","./renderer":"src/renderer.ts","./types":"src/types.ts"},"src/state.ts":{"./types":"src/types.ts"},"src/player.ts":{"./types":"src/types.ts"},"src/world.ts":{"./types":"src/types.ts"},"src/collision.ts":{"./types":"src/types.ts"},"src/particles.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference path="../globals.d.ts" />
const state_1 = require("./state");
const player_1 = require("./player");
const world_1 = require("./world");
const collision_1 = require("./collision");
const audio_1 = require("./audio");
const particles_1 = require("./particles");
const hud_1 = require("./hud");
const renderer_1 = require("./renderer");
// Module-level references
let renderer;
let playerCraft;
let world;
let collision;
let audio;
let particles;
let hud;
let playerState;
let gameState;
let inputState;
let lastTime = 0;
let animFrameId = 0;
let scoreTimer = 0;
function setupInput() {
    window.addEventListener('keydown', (e) => {
        state_1.keys[e.code] = true;
        // Prevent scrolling
        if (['Space', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.code)) {
            e.preventDefault();
        }
    });
    window.addEventListener('keyup', (e) => {
        state_1.keys[e.code] = false;
    });
    // Touch controls
    let touchStartX = 0;
    let touchStartY = 0;
    window.addEventListener('touchstart', (e) => {
        touchStartX = e.touches[0].clientX;
        touchStartY = e.touches[0].clientY;
    }, { passive: true });
    window.addEventListener('touchmove', (e) => {
        const dx = e.touches[0].clientX - touchStartX;
        const dy = e.touches[0].clientY - touchStartY;
        state_1.keys['ArrowLeft'] = dx < -30;
        state_1.keys['ArrowRight'] = dx > 30;
        state_1.keys['ArrowUp'] = dy < -30;
        state_1.keys['ArrowDown'] = dy > 30;
    }, { passive: true });
    window.addEventListener('touchend', () => {
        state_1.keys['ArrowLeft'] = false;
        state_1.keys['ArrowRight'] = false;
        state_1.keys['ArrowUp'] = false;
        state_1.keys['ArrowDown'] = false;
    });
}
function getInput() {
    return {
        left: state_1.keys['ArrowLeft'] || state_1.keys['KeyA'],
        right: state_1.keys['ArrowRight'] || state_1.keys['KeyD'],
        up: state_1.keys['ArrowUp'] || state_1.keys['KeyW'],
        down: state_1.keys['ArrowDown'] || state_1.keys['KeyS'],
        boost: state_1.keys['Space'],
    };
}
function startGame() {
    // Cancel old loops
    if (animFrameId)
        cancelAnimationFrame(animFrameId);
    // Clear canvas container
    const cc = document.getElementById('canvas-container');
    if (cc)
        cc.innerHTML = '';
    // Reset state
    playerState = (0, state_1.createPlayer)();
    gameState = (0, state_1.createGameState)();
    gameState.started = true;
    // Create fresh renderer
    const camState = (0, state_1.createCameraState)();
    renderer = new renderer_1.Renderer(state_1.CONFIG, camState);
    // Create fresh systems
    playerCraft = new player_1.PlayerCraft(state_1.CONFIG);
    renderer.scene.add(playerCraft.mesh);
    world = new world_1.WorldGenerator(state_1.CONFIG, renderer.scene);
    collision = new collision_1.CollisionSystem(state_1.CONFIG);
    particles = new particles_1.ParticleManager(renderer.scene, state_1.CONFIG);
    // Generate initial chunks
    for (let i = 0; i < state_1.CONFIG.chunkCount; i++) {
        world.generateChunk(i * state_1.CONFIG.chunkLength, 1);
    }
    // Reset player
    playerCraft.reset(playerState);
    // Hide screens
    hud.hideStartScreen();
    hud.hideGameOver();
    // Start audio
    audio.resume();
    audio.startEngine();
    // Start game loop
    lastTime = performance.now();
    gameLoop(performance.now());
}
function gameOver() {
    gameState.gameOver = true;
    playerState.alive = false;
    audio.playCrash();
    audio.playGameOver();
    audio.stopEngine();
    // Crash particles
    particles.emitCrash(playerState.position);
    // Shake camera
    renderer.triggerShake(4);
    // Show game over after brief delay
    setTimeout(() => {
        hud.showGameOver(gameState.score, gameState.maxSpeed, gameState.maxMultiplier);
    }, 800);
}
function gameLoop(timestamp) {
    animFrameId = requestAnimationFrame(gameLoop);
    const dt = Math.min((timestamp - lastTime) / 1000, 0.05); // Cap dt to prevent spiral
    lastTime = timestamp;
    if (!gameState.started || gameState.gameOver) {
        // Still render for visual effect
        renderer.render();
        return;
    }
    // Get input
    inputState = getInput();
    // --- UPDATE ---
    // Move player forward
    const moveAmount = playerState.speed * dt;
    playerState.position.z += moveAmount;
    gameState.distance += moveAmount;
    // Energy decay
    playerState.energy -= state_1.CONFIG.energyDecay * dt;
    if (playerState.energy <= 0) {
        playerState.energy = 0;
        gameOver();
        renderer.render();
        return;
    }
    // Turbo management
    if (inputState.boost && playerState.turbo > 10 && !playerState.isBoosting) {
        playerState.isBoosting = true;
        audio.playBoost();
        renderer.triggerShake(0.5);
    }
    if (playerState.isBoosting) {
        playerState.turbo -= state_1.CONFIG.turboDecay * dt;
        if (playerState.turbo <= 0) {
            playerState.turbo = 0;
            playerState.isBoosting = false;
        }
    }
    else {
        // Charge turbo while going fast
        if (playerState.speed > state_1.CONFIG.maxSpeed * 0.6) {
            playerState.turbo = Math.min(playerState.maxTurbo, playerState.turbo + state_1.CONFIG.turboChargeRate * dt);
        }
    }
    // Update player physics
    playerCraft.update(dt, inputState, playerState);
    // Track max speed
    if (playerState.speed > gameState.maxSpeed) {
        gameState.maxSpeed = playerState.speed;
    }
    // Update world chunks
    world.updateChunks(playerState.position.z);
    // Get active obstacles and orbs
    const obstacles = world.getActiveObstacles(playerState.position.z);
    const orbs = world.getActiveOrbs(playerState.position.z);
    // Check collisions
    if (collision.checkObstacleCollisions(playerState, obstacles)) {
        gameOver();
        renderer.render();
        return;
    }
    // Check near misses
    const nearMissResult = collision.checkNearMisses(playerState, obstacles);
    if (nearMissResult.hasNearMiss && !gameState.nearMissActive) {
        gameState.nearMissActive = true;
        gameState.nearMissTimer = state_1.CONFIG.nearMissDuration;
        gameState.multiplier = Math.min(10, gameState.multiplier + state_1.CONFIG.nearMissMultiplierGain);
        if (gameState.multiplier > gameState.maxMultiplier) {
            gameState.maxMultiplier = gameState.multiplier;
        }
        hud.updateMultiplier(gameState.multiplier, true);
        hud.flashNearMiss();
        audio.playNearMiss();
        renderer.triggerShake(1.0);
    }
    // Near miss timer decay
    if (gameState.nearMissActive) {
        gameState.nearMissTimer -= dt;
        if (gameState.nearMissTimer <= 0) {
            gameState.nearMissActive = false;
            // Gradual multiplier decay
            gameState.multiplier = Math.max(1, gameState.multiplier - dt * 0.3);
            hud.updateMultiplier(gameState.multiplier, false);
        }
    }
    // Collect orbs
    const orbsCollected = collision.checkOrbCollection(playerState, orbs);
    if (orbsCollected > 0) {
        playerState.energy = Math.min(playerState.maxEnergy, playerState.energy + state_1.CONFIG.energyRecharge * orbsCollected);
        gameState.score += orbsCollected * 100 * gameState.multiplier;
        audio.playCollect();
        particles.emitBurst(playerState.position, 0xffaa00, 10);
    }
    // Score accumulation
    scoreTimer += dt;
    if (scoreTimer >= 0.5) {
        scoreTimer -= 0.5;
        gameState.score += playerState.speed * gameState.multiplier * 0.1;
    }
    // Difficulty scaling
    gameState.difficulty = 1 + gameState.distance * 0.0003;
    // Animate world
    world.animateOrbs(dt);
    // Particles
    particles.emitTrail(playerState.position, playerState.speed);
    particles.emitDebris(playerState.position);
    particles.update(dt, playerState.position.z);
    // Update camera
    renderer.updateCamera(playerState.position, playerState.speed, dt);
    // Update audio
    audio.updateEngine(playerState.speed, state_1.CONFIG.maxSpeed);
    // --- RENDER ---
    renderer.render();
    // --- HUD ---
    hud.updateSpeed(playerState.speed, state_1.CONFIG.maxSpeed * 1.5);
    hud.updateEnergy(playerState.energy, playerState.maxEnergy);
    hud.updateTurbo(playerState.turbo, playerState.maxTurbo, playerState.isBoosting);
    hud.updateScore(gameState.score);
}
// --- ENTRY ---
// --- ENTRY ---
function init() {
    // Initialize systems
    audio = new audio_1.GameAudio();
    hud = new hud_1.HUD();
    // Setup input
    setupInput();
    // Create initial renderer and world (for background)
    const camState = (0, state_1.createCameraState)();
    renderer = new renderer_1.Renderer(state_1.CONFIG, camState);
    world = new world_1.WorldGenerator(state_1.CONFIG, renderer.scene);
    playerCraft = new player_1.PlayerCraft(state_1.CONFIG);
    renderer.scene.add(playerCraft.mesh);
    // Generate initial chunks
    for (let i = 0; i < state_1.CONFIG.chunkCount; i++) {
        world.generateChunk(i * state_1.CONFIG.chunkLength, 1);
    }
    // Show start screen
    hud.showStartScreen();
    // Start button callback
    hud.onStart(() => {
        audio.init();
        startGame();
    });
    // Restart button callback
    hud.onRestart(() => {
        startGame();
    });
    // Start render loop for background
    lastTime = performance.now();
    backgroundLoop(performance.now());
}
function backgroundLoop(timestamp) {
    if (gameState && gameState.started)
        return; // Game loop takes over
    requestAnimationFrame(backgroundLoop);
    const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
    lastTime = timestamp;
    // Slow ambient camera movement
    const t = timestamp * 0.0001;
    renderer.camera.position.x = Math.sin(t) * 5;
    renderer.camera.position.y = 5 + Math.sin(t * 0.7) * 2;
    renderer.camera.position.z = -15;
    const lookAt = new THREE.Vector3(0, 0, 100);
    renderer.camera.lookAt(lookAt);
    world.animateOrbs(dt);
    renderer.render();
}
// Auto-run entry
init();
};

// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};

// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
/// <reference path="../globals.d.ts" />
// Game type definitions
Object.defineProperty(exports, "__esModule", { value: true });
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.touchPos = exports.touchActive = exports.mousePos = exports.keys = exports.CONFIG = void 0;
exports.createPlayer = createPlayer;
exports.createInput = createInput;
exports.createGameState = createGameState;
exports.createCameraState = createCameraState;
exports.CONFIG = {
    baseSpeed: 80,
    maxSpeed: 250,
    acceleration: 40,
    turnSpeed: 2.5,
    pitchSpeed: 2.0,
    bankFactor: 0.6,
    energyDecay: 3.5,
    energyRecharge: 25,
    chunkLength: 200,
    chunkCount: 8,
    canyonWidth: 120,
    canyonHeight: 80,
    nearMissDistance: 18,
    nearMissDuration: 1.5,
    nearMissMultiplierGain: 0.5,
    turboBoostSpeed: 150,
    turboDecay: 8,
    turboChargeRate: 2,
};
function createPlayer() {
    return {
        position: new THREE.Vector3(0, 0, 0),
        velocity: new THREE.Vector3(0, 0, 0),
        rotation: { roll: 0, pitch: 0, yaw: 0 },
        speed: exports.CONFIG.baseSpeed,
        energy: 100,
        maxEnergy: 100,
        turbo: 0,
        maxTurbo: 100,
        isBoosting: false,
        alive: true,
    };
}
function createInput() {
    return { left: false, right: false, up: false, down: false, boost: false };
}
function createGameState() {
    return {
        score: 0,
        multiplier: 1,
        maxMultiplier: 1,
        maxSpeed: 0,
        distance: 0,
        difficulty: 1,
        nearMissTimer: 0,
        nearMissActive: false,
        gameOver: false,
        paused: false,
        started: false,
    };
}
function createCameraState() {
    return {
        shake: 0,
        shakeDecay: 0.9,
        shakeDuration: 0,
        fov: 70,
        targetFov: 70,
        distance: 15,
        height: 5,
    };
}
exports.keys = {};
exports.mousePos = { x: 0, y: 0 };
exports.touchActive = false;
exports.touchPos = { x: 0, y: 0 };
};

// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlayerCraft = void 0;
class PlayerCraft {
    constructor(config) {
        this.config = config;
        this.trailPoints = [];
        this.mesh = this.buildCraft();
        this.mesh.castShadow = true;
    }
    buildCraft() {
        const group = new THREE.Group();
        // Main body - sleek arrow shape
        const bodyGeo = new THREE.ConeGeometry(1.2, 5, 6);
        const bodyMat = new THREE.MeshPhongMaterial({
            color: 0xcccccc,
            emissive: 0x333333,
            flatShading: true,
            specular: 0x666666,
            shininess: 30,
        });
        const body = new THREE.Mesh(bodyGeo, bodyMat);
        body.rotation.x = Math.PI / 2;
        body.receiveShadow = true;
        group.add(body);
        // Nose cone
        const noseGeo = new THREE.ConeGeometry(0.6, 2, 6);
        const noseMat = new THREE.MeshPhongMaterial({
            color: 0xff6633,
            emissive: 0xff3300,
            emissiveIntensity: 0.3,
            flatShading: true,
            specular: 0xff8844,
            shininess: 50,
        });
        const nose = new THREE.Mesh(noseGeo, noseMat);
        nose.rotation.x = Math.PI / 2;
        nose.position.z = -3.5;
        nose.receiveShadow = true;
        group.add(nose);
        // Wings - left
        const wingGeo = new THREE.BoxGeometry(4, 0.15, 2);
        const wingMat = new THREE.MeshPhongMaterial({
            color: 0x999999,
            emissive: 0x222222,
            flatShading: true,
        });
        const leftWing = new THREE.Mesh(wingGeo, wingMat);
        leftWing.position.set(-2.2, -0.2, -0.5);
        leftWing.rotation.z = 0.15;
        leftWing.receiveShadow = true;
        group.add(leftWing);
        // Wings - right
        const rightWing = new THREE.Mesh(wingGeo, wingMat);
        rightWing.position.set(2.2, -0.2, -0.5);
        rightWing.rotation.z = -0.15;
        rightWing.receiveShadow = true;
        group.add(rightWing);
        // Wing tips (glowing)
        const tipGeo = new THREE.BoxGeometry(0.3, 0.3, 0.3);
        const tipMat = new THREE.MeshPhongMaterial({
            color: 0xff4400,
            emissive: 0xff2200,
            emissiveIntensity: 1.5,
            flatShading: true,
        });
        const leftTip = new THREE.Mesh(tipGeo, tipMat);
        leftTip.position.set(-4.1, -0.2, -0.5);
        group.add(leftTip);
        const rightTip = new THREE.Mesh(tipGeo, tipMat);
        rightTip.position.set(4.1, -0.2, -0.5);
        group.add(rightTip);
        // Engine glow
        const engineGeo = new THREE.SphereGeometry(0.8, 8, 6);
        const engineMat = new THREE.MeshBasicMaterial({
            color: 0xff6600,
            transparent: true,
            opacity: 0.9,
        });
        this.engineGlow = new THREE.Mesh(engineGeo, engineMat);
        this.engineGlow.position.z = 2.5;
        group.add(this.engineGlow);
        // Engine core (brighter)
        const coreGeo = new THREE.SphereGeometry(0.4, 8, 6);
        const coreMat = new THREE.MeshBasicMaterial({
            color: 0xffdd44,
            transparent: true,
            opacity: 1,
        });
        const core = new THREE.Mesh(coreGeo, coreMat);
        core.position.z = 2.5;
        group.add(core);
        return group;
    }
    update(dt, input, state) {
        if (!state.alive)
            return;
        // Speed control based on pitch input
        let targetSpeed = state.speed;
        if (input.up) {
            targetSpeed = Math.min(targetSpeed + this.config.acceleration * dt, this.config.maxSpeed);
        }
        else if (input.down) {
            targetSpeed = Math.max(targetSpeed - this.config.acceleration * dt * 1.5, this.config.baseSpeed * 0.5);
        }
        // Boost adds extra speed
        if (state.isBoosting) {
            targetSpeed = Math.min(targetSpeed + this.config.turboBoostSpeed * dt * 0.5, this.config.maxSpeed * 1.5);
        }
        // Smooth speed transition
        state.speed += (targetSpeed - state.speed) * Math.min(1, dt * 5);
        // Lateral movement with momentum
        const turnForce = this.config.turnSpeed;
        const pitchForce = this.config.pitchSpeed;
        if (input.left) {
            state.velocity.x -= turnForce * dt;
        }
        if (input.right) {
            state.velocity.x += turnForce * dt;
        }
        if (input.up) {
            state.velocity.y += pitchForce * dt * 0.5;
        }
        if (input.down) {
            state.velocity.y -= pitchForce * dt * 0.5;
        }
        // Damping (air resistance)
        const damping = Math.pow(0.92, dt * 60);
        state.velocity.x *= damping;
        state.velocity.y *= damping;
        // Apply velocity to position
        state.position.x += state.velocity.x * dt;
        state.position.y += state.velocity.y * dt;
        // Clamp position to canyon bounds
        const halfWidth = this.config.canyonWidth * 0.42;
        const halfHeight = this.config.canyonHeight * 0.42;
        state.position.x = Math.max(-halfWidth, Math.min(halfWidth, state.position.x));
        state.position.y = Math.max(-halfHeight, Math.min(halfHeight, state.position.y));
        // Banking (roll) based on lateral velocity
        const targetRoll = -state.velocity.x * this.config.bankFactor * 0.08;
        state.rotation.roll += (targetRoll - state.rotation.roll) * Math.min(1, dt * 8);
        // Pitch based on vertical velocity
        const targetPitch = -state.velocity.y * 0.04;
        state.rotation.pitch += (targetPitch - state.rotation.pitch) * Math.min(1, dt * 6);
        // Apply rotation to mesh
        this.mesh.position.copy(state.position);
        this.mesh.rotation.x = state.rotation.pitch;
        this.mesh.rotation.y = state.rotation.yaw;
        this.mesh.rotation.z = state.rotation.roll;
        // Engine glow pulsation
        const glowScale = 1 + Math.sin(Date.now() * 0.01) * 0.2;
        const speedFactor = state.speed / this.config.maxSpeed;
        this.engineGlow.scale.set(glowScale + speedFactor * 0.5, glowScale + speedFactor * 0.5, glowScale * 1.5 + speedFactor);
        // Engine color shifts with speed
        const engineColor = this.engineGlow.material;
        if (state.isBoosting) {
            engineColor.color.setHex(0x44ddff);
            engineColor.opacity = 0.95 + Math.sin(Date.now() * 0.02) * 0.05;
        }
        else if (speedFactor > 0.7) {
            engineColor.color.setHex(0xff4400);
        }
        else {
            engineColor.color.setHex(0xff6600);
        }
        // Trail points
        if (this.trailPoints.length === 0 ||
            this.trailPoints[this.trailPoints.length - 1].distanceTo(state.position) > 1.5) {
            this.trailPoints.push(state.position.clone());
            if (this.trailPoints.length > 60) {
                this.trailPoints.shift();
            }
        }
    }
    getTrailPoints() {
        return this.trailPoints;
    }
    reset(state) {
        state.position.set(0, 0, 0);
        state.velocity.set(0, 0, 0);
        state.rotation = { roll: 0, pitch: 0, yaw: 0 };
        state.speed = this.config.baseSpeed;
        state.energy = state.maxEnergy;
        state.turbo = 0;
        state.isBoosting = false;
        state.alive = true;
        this.trailPoints = [];
        this.mesh.position.set(0, 0, 0);
        this.mesh.rotation.x = 0;
        this.mesh.rotation.y = 0;
        this.mesh.rotation.z = 0;
    }
}
exports.PlayerCraft = PlayerCraft;
};

// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorldGenerator = void 0;
class WorldGenerator {
    constructor(config, scene) {
        this.chunks = [];
        this.chunkIndex = 0;
        this.config = config;
        this.scene = scene;
        // Cyber-sunset palette variations - brighter, more dramatic
        this.colors = [
            { wall: 0x9a5068, ground: 0x4a2838, sky: 0x1a0a20, accent: 0xff6633 },
            { wall: 0xaa6070, ground: 0x4a2848, sky: 0x0a0018, accent: 0xff4422 },
            { wall: 0xb06878, ground: 0x503048, sky: 0x100820, accent: 0xff8844 },
            { wall: 0x905060, ground: 0x402838, sky: 0x100a20, accent: 0xff5533 },
        ];
    }
    generateChunk(z, difficulty) {
        const chunk = {
            z: z,
            meshes: [],
            orbs: [],
            obstacles: [],
            loaded: true,
            active: true,
        };
        const colorSet = this.colors[Math.floor(Math.random() * this.colors.length)];
        const canyonWidth = this.config.canyonWidth * (1 - difficulty * 0.005);
        const canyonHeight = this.config.canyonHeight * (1 - difficulty * 0.003);
        // Generate canyon walls for this chunk
        this.generateCanyonWalls(chunk, z, canyonWidth, canyonHeight, colorSet, difficulty);
        // Generate floating monoliths
        const monolithCount = Math.floor(1 + difficulty * 0.3);
        for (let i = 0; i < monolithCount; i++) {
            this.generateMonolith(chunk, z + Math.random() * this.config.chunkLength, canyonWidth, canyonHeight, colorSet);
        }
        // Generate tight squeezes
        if (difficulty > 2 && Math.random() < 0.3 * difficulty * 0.1) {
            this.generateSqueeze(chunk, z + Math.random() * this.config.chunkLength, canyonWidth, canyonHeight, colorSet);
        }
        // Generate collectible orbs
        const orbCount = 3 + Math.floor(Math.random() * 3);
        for (let i = 0; i < orbCount; i++) {
            this.generateOrb(chunk, z + Math.random() * this.config.chunkLength, canyonWidth * 0.5, canyonHeight * 0.5);
        }
        return chunk;
    }
    generateCanyonWalls(chunk, z, width, height, colorSet, difficulty) {
        const segments = 8;
        const segLength = this.config.chunkLength / segments;
        for (let i = 0; i < segments; i++) {
            const segZ = z + i * segLength;
            // Wall height variation
            const leftHeight = height * (0.4 + Math.random() * 0.6) * (1 + difficulty * 0.05);
            const rightHeight = height * (0.4 + Math.random() * 0.6) * (1 + difficulty * 0.05);
            // Wall thickness
            const thickness = 8 + Math.random() * 12;
            // Left wall - brighter with gradient
            const leftWallGeo = new THREE.BoxGeometry(thickness, leftHeight, segLength * 0.9);
            const leftWallMat = new THREE.MeshLambertMaterial({
                color: colorSet.wall,
                emissive: 0x221111,
                emissiveIntensity: 0.1,
                flatShading: true,
            });
            const leftWall = new THREE.Mesh(leftWallGeo, leftWallMat);
            leftWall.position.set(-width / 2 - thickness / 2, leftHeight / 2 - height * 0.1, segZ);
            leftWall.castShadow = true;
            leftWall.receiveShadow = true;
            this.scene.add(leftWall);
            chunk.meshes.push(leftWall);
            // Right wall
            const rightWallGeo = new THREE.BoxGeometry(thickness, rightHeight, segLength * 0.9);
            const rightWallMat = new THREE.MeshLambertMaterial({
                color: colorSet.wall,
                emissive: 0x221111,
                emissiveIntensity: 0.1,
                flatShading: true,
            });
            const rightWall = new THREE.Mesh(rightWallGeo, rightWallMat);
            rightWall.position.set(width / 2 + thickness / 2, rightHeight / 2 - height * 0.1, segZ);
            rightWall.castShadow = true;
            rightWall.receiveShadow = true;
            this.scene.add(rightWall);
            chunk.meshes.push(rightWall);
            // Wall accent strips (glowing edges)
            if (Math.random() < 0.4) {
                const stripGeo = new THREE.BoxGeometry(0.5, leftHeight * 0.8, segLength * 0.3);
                const stripMat = new THREE.MeshBasicMaterial({
                    color: colorSet.accent,
                    transparent: true,
                    opacity: 0.4,
                });
                const strip = new THREE.Mesh(stripGeo, stripMat);
                strip.position.set(-width / 2, leftHeight / 2 - height * 0.1, segZ);
                this.scene.add(strip);
                chunk.meshes.push(strip);
            }
            // Ceiling segments (occasional)
            if (Math.random() < 0.3 + difficulty * 0.02) {
                const ceilGeo = new THREE.BoxGeometry(width, 3, segLength * 0.5);
                const ceilMat = new THREE.MeshLambertMaterial({
                    color: colorSet.wall,
                    flatShading: true,
                });
                const ceil = new THREE.Mesh(ceilGeo, ceilMat);
                ceil.position.set(0, height * 0.4, segZ);
                ceil.castShadow = true;
                ceil.receiveShadow = true;
                this.scene.add(ceil);
                chunk.meshes.push(ceil);
            }
            // Floor
            const floorGeo = new THREE.BoxGeometry(width, 2, segLength);
            const floorMat = new THREE.MeshLambertMaterial({
                color: colorSet.ground,
                emissive: 0x110811,
                emissiveIntensity: 0.05,
                flatShading: true,
            });
            const floor = new THREE.Mesh(floorGeo, floorMat);
            floor.position.set(0, -height * 0.45, segZ);
            floor.receiveShadow = true;
            this.scene.add(floor);
            chunk.meshes.push(floor);
            // Stalactites from ceiling
            if (Math.random() < 0.3) {
                const stalHeight = 2 + Math.random() * 6;
                const stalGeo = new THREE.ConeGeometry(0.5 + Math.random(), stalHeight, 4);
                const stalMat = new THREE.MeshLambertMaterial({
                    color: colorSet.wall,
                    flatShading: true,
                });
                const stal = new THREE.Mesh(stalGeo, stalMat);
                stal.position.set((Math.random() - 0.5) * width * 0.8, height * 0.35 - stalHeight / 2, segZ);
                stal.rotation.x = Math.PI;
                this.scene.add(stal);
                chunk.meshes.push(stal);
            }
        }
    }
    generateMonolith(chunk, z, width, height, colorSet) {
        const h = 10 + Math.random() * 20;
        const w = 3 + Math.random() * 5;
        const d = 3 + Math.random() * 5;
        const xPos = (Math.random() - 0.5) * width * 0.7;
        const yPos = (Math.random() - 0.5) * height * 0.5;
        const geo = new THREE.BoxGeometry(w, h, d);
        const mat = new THREE.MeshLambertMaterial({
            color: colorSet.accent,
            emissive: colorSet.accent,
            emissiveIntensity: 0.15,
            flatShading: true,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.set(xPos, yPos, z);
        mesh.rotation.y = Math.random() * Math.PI;
        mesh.castShadow = true;
        mesh.receiveShadow = true;
        this.scene.add(mesh);
        chunk.meshes.push(mesh);
        chunk.obstacles.push({
            type: 'monolith',
            position: new THREE.Vector3(xPos, yPos, z),
            bounds: {
                minX: xPos - w / 2, maxX: xPos + w / 2,
                minY: yPos - h / 2, maxY: yPos + h / 2,
                width: w, height: h, depth: d,
            },
            mesh: mesh,
            passed: false,
            nearMissChecked: false,
        });
    }
    generateSqueeze(chunk, z, width, height, colorSet) {
        // Two walls with a narrow gap
        const gapSize = 8 + Math.random() * 6;
        const gapY = (Math.random() - 0.5) * height * 0.3;
        // Top wall
        const topGeo = new THREE.BoxGeometry(width * 0.8, height * 0.3, 15);
        const topMat = new THREE.MeshLambertMaterial({
            color: colorSet.wall,
            flatShading: true,
        });
        const top = new THREE.Mesh(topGeo, topMat);
        top.position.set(0, gapY + gapSize / 2 + height * 0.15, z);
        top.castShadow = true;
        top.receiveShadow = true;
        this.scene.add(top);
        chunk.meshes.push(top);
        // Bottom wall
        const botGeo = new THREE.BoxGeometry(width * 0.8, height * 0.3, 15);
        const botMat = new THREE.MeshLambertMaterial({
            color: colorSet.wall,
            flatShading: true,
        });
        const bot = new THREE.Mesh(botGeo, botMat);
        bot.position.set(0, gapY - gapSize / 2 - height * 0.15, z);
        bot.castShadow = true;
        bot.receiveShadow = true;
        this.scene.add(bot);
        chunk.meshes.push(bot);
        chunk.obstacles.push({
            type: 'squeeze',
            position: new THREE.Vector3(0, gapY, z),
            bounds: {
                minX: -width * 0.4, maxX: width * 0.4,
                minY: gapY - gapSize / 2, maxY: gapY + gapSize / 2,
                width: width * 0.8, height: gapSize, depth: 15,
            },
            mesh: top,
            passed: false,
            nearMissChecked: false,
        });
    }
    generateOrb(chunk, z, halfWidth, halfHeight) {
        const x = (Math.random() - 0.5) * halfWidth * 1.2;
        const y = (Math.random() - 0.5) * halfHeight * 1.0;
        const geo = new THREE.OctahedronGeometry(1.2, 0);
        const mat = new THREE.MeshPhongMaterial({
            color: 0xffaa00,
            emissive: 0xff8800,
            emissiveIntensity: 1.5,
            flatShading: true,
            transparent: true,
            opacity: 0.9,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.set(x, y, z);
        this.scene.add(mesh);
        chunk.meshes.push(mesh);
        // Glow sprite around orb
        const glowCanvas = document.createElement('canvas');
        glowCanvas.width = 64;
        glowCanvas.height = 64;
        const ctx = glowCanvas.getContext('2d');
        const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
        gradient.addColorStop(0, 'rgba(255, 170, 0, 0.8)');
        gradient.addColorStop(0.3, 'rgba(255, 120, 0, 0.4)');
        gradient.addColorStop(1, 'rgba(255, 80, 0, 0)');
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, 64, 64);
        const glowTex = new THREE.CanvasTexture(glowCanvas);
        const glowMat = new THREE.SpriteMaterial({
            map: glowTex,
            transparent: true,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
        });
        const glowSprite = new THREE.Sprite(glowMat);
        glowSprite.scale.set(6, 6, 1);
        mesh.add(glowSprite);
        chunk.orbs.push({
            position: new THREE.Vector3(x, y, z),
            mesh: mesh,
            collected: false,
            glowPhase: Math.random() * Math.PI * 2,
            value: 1,
        });
    }
    updateChunks(playerZ) {
        // Remove chunks behind player
        const removeThreshold = this.config.chunkLength * 2;
        const chunksToRemove = this.chunks.filter(c => c.z < playerZ - removeThreshold);
        for (const chunk of chunksToRemove) {
            for (const mesh of chunk.meshes) {
                this.scene.remove(mesh);
                if (mesh.geometry) {
                    mesh.geometry.dispose?.();
                }
                if (mesh.material) {
                    mesh.material.dispose?.();
                }
            }
            chunk.active = false;
        }
        this.chunks = this.chunks.filter(c => c.z >= playerZ - removeThreshold);
        // Add new chunks ahead
        let maxChunkZ = this.chunks.length > 0
            ? Math.max(...this.chunks.map(c => c.z))
            : 0;
        while (maxChunkZ < playerZ + this.config.chunkLength * this.config.chunkCount) {
            const newChunk = this.generateChunk(maxChunkZ, this.chunks.length * 0.5);
            this.chunks.push(newChunk);
            maxChunkZ += this.config.chunkLength;
        }
    }
    getActiveObstacles(playerZ) {
        const range = this.config.chunkLength * 2;
        const result = [];
        for (const chunk of this.chunks) {
            if (chunk.z >= playerZ - range && chunk.z <= playerZ + range) {
                result.push(...chunk.obstacles);
            }
        }
        return result;
    }
    getActiveOrbs(playerZ) {
        const range = this.config.chunkLength * 2;
        const result = [];
        for (const chunk of this.chunks) {
            if (chunk.z >= playerZ - range && chunk.z <= playerZ + range) {
                result.push(...chunk.orbs);
            }
        }
        return result;
    }
    getActiveWallMeshes(playerZ) {
        const range = this.config.chunkLength * 3;
        const result = [];
        for (const chunk of this.chunks) {
            if (chunk.z >= playerZ - range && chunk.z <= playerZ + range) {
                result.push(...chunk.meshes);
            }
        }
        return result;
    }
    reset(scene) {
        for (const chunk of this.chunks) {
            for (const mesh of chunk.meshes) {
                scene.remove(mesh);
            }
        }
        this.chunks = [];
        this.chunkIndex = 0;
    }
    animateOrbs(dt) {
        for (const chunk of this.chunks) {
            for (const orb of chunk.orbs) {
                if (orb.collected)
                    continue;
                orb.glowPhase += dt * 3;
                orb.mesh.rotation.y += dt * 2;
                orb.mesh.rotation.x += dt * 1;
                const pulse = 1 + Math.sin(orb.glowPhase) * 0.2;
                orb.mesh.scale.set(pulse, pulse, pulse);
            }
        }
    }
}
exports.WorldGenerator = WorldGenerator;
};

// ── module: src/collision.ts ──
__mods["src/collision.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CollisionSystem = void 0;
class CollisionSystem {
    constructor(config) {
        this.playerRadius = 2.5;
        this.config = config;
    }
    checkObstacleCollisions(player, obstacles) {
        if (!player.alive)
            return false;
        for (const obs of obstacles) {
            const b = obs.bounds;
            const px = player.position.x;
            const py = player.position.y;
            const pz = player.position.z;
            // AABB vs sphere check
            const closestX = Math.max(b.minX, Math.min(px, b.maxX));
            const closestY = Math.max(b.minY, Math.min(py, b.maxY));
            const closestZ = Math.max(obs.position.z - b.depth / 2, Math.min(pz, obs.position.z + b.depth / 2));
            const dx = px - closestX;
            const dy = py - closestY;
            const dz = pz - closestZ;
            const distSq = dx * dx + dy * dy + dz * dz;
            if (distSq < this.playerRadius * this.playerRadius) {
                return true; // Collision!
            }
        }
        return false;
    }
    checkNearMisses(player, obstacles) {
        let closestDist = Infinity;
        let hasNearMiss = false;
        for (const obs of obstacles) {
            if (obs.nearMissChecked)
                continue;
            const b = obs.bounds;
            const px = player.position.x;
            const py = player.position.y;
            const pz = player.position.z;
            const closestX = Math.max(b.minX, Math.min(px, b.maxX));
            const closestY = Math.max(b.minY, Math.min(py, b.maxY));
            const closestZ = Math.max(obs.position.z - b.depth / 2, Math.min(pz, obs.position.z + b.depth / 2));
            const dx = px - closestX;
            const dy = py - closestY;
            const dz = pz - closestZ;
            const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
            if (dist < closestDist) {
                closestDist = dist;
            }
            // Near miss check
            if (dist < this.config.nearMissDistance && dist > this.playerRadius) {
                hasNearMiss = true;
                obs.nearMissChecked = true;
            }
            // Mark as passed if player is beyond it
            if (pz > obs.position.z + b.depth / 2 + 5) {
                obs.passed = true;
                obs.nearMissChecked = true;
            }
        }
        return { hasNearMiss, closestDist };
    }
    checkOrbCollection(player, orbs) {
        let collected = 0;
        const collectRadius = 4;
        for (const orb of orbs) {
            if (orb.collected)
                continue;
            const dx = player.position.x - orb.position.x;
            const dy = player.position.y - orb.position.y;
            const dz = player.position.z - orb.position.z;
            const distSq = dx * dx + dy * dy + dz * dz;
            if (distSq < collectRadius * collectRadius) {
                orb.collected = true;
                orb.mesh.visible = false;
                collected += orb.value;
            }
        }
        return collected;
    }
    checkWallProximity(player) {
        const halfWidth = this.config.canyonWidth * 0.42;
        const halfHeight = this.config.canyonHeight * 0.42;
        const wallDistX = halfWidth - Math.abs(player.position.x);
        const wallDistY = halfHeight - Math.abs(player.position.y);
        return Math.min(wallDistX, wallDistY);
    }
}
exports.CollisionSystem = CollisionSystem;
};

// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GameAudio = void 0;
class GameAudio {
    constructor() {
        this.ctx = null;
        this.engineOsc = null;
        this.engineGain = null;
        this.masterGain = null;
        this.enabled = false;
    }
    init() {
        try {
            this.ctx = new AudioContext();
            this.masterGain = this.ctx.createGain();
            this.masterGain.gain.value = 0.3;
            this.masterGain.connect(this.ctx.destination);
            this.enabled = true;
        }
        catch (e) {
            console.warn('Web Audio not available:', e);
        }
    }
    resume() {
        if (this.ctx && this.ctx.state === 'suspended') {
            this.ctx.resume();
        }
    }
    startEngine() {
        if (!this.ctx || !this.masterGain)
            return;
        // Low engine hum
        this.engineOsc = this.ctx.createOscillator();
        this.engineOsc.type = 'sawtooth';
        this.engineOsc.frequency.value = 55;
        this.engineGain = this.ctx.createGain();
        this.engineGain.gain.value = 0.08;
        // Low-pass filter for engine rumble
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 200;
        filter.Q.value = 2;
        this.engineOsc.connect(filter);
        filter.connect(this.engineGain);
        this.engineGain.connect(this.masterGain);
        this.engineOsc.start();
    }
    updateEngine(speed, maxSpeed) {
        if (!this.engineOsc || !this.engineGain)
            return;
        const ratio = speed / maxSpeed;
        this.engineOsc.frequency.value = 40 + ratio * 80;
        this.engineGain.gain.value = 0.05 + ratio * 0.1;
    }
    stopEngine() {
        if (this.engineOsc) {
            this.engineOsc.stop();
            this.engineOsc.disconnect();
            this.engineOsc = null;
        }
        if (this.engineGain) {
            this.engineGain.disconnect();
            this.engineGain = null;
        }
    }
    playCollect() {
        if (!this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(880, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(1760, this.ctx.currentTime + 0.1);
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.25);
    }
    playNearMiss() {
        if (!this.ctx || !this.masterGain)
            return;
        // Whoosh sound
        const bufferSize = this.ctx.sampleRate * 0.3;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
            data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.15));
        }
        const source = this.ctx.createBufferSource();
        source.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'bandpass';
        filter.frequency.value = 1200;
        filter.Q.value = 3;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.3);
        source.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        source.start();
    }
    playBoost() {
        if (!this.ctx || !this.masterGain)
            return;
        const osc = this.ctx.createOscillator();
        osc.type = 'sawtooth';
        osc.frequency.setValueAtTime(100, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(400, this.ctx.currentTime + 0.3);
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.12, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.02, this.ctx.currentTime + 0.5);
        osc.connect(gain);
        gain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.6);
    }
    playCrash() {
        if (!this.ctx || !this.masterGain)
            return;
        // Explosion noise
        const bufferSize = this.ctx.sampleRate * 0.8;
        const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
            data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.3));
        }
        const source = this.ctx.createBufferSource();
        source.buffer = buffer;
        const filter = this.ctx.createBiquadFilter();
        filter.type = 'lowpass';
        filter.frequency.value = 800;
        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.8);
        source.connect(filter);
        filter.connect(gain);
        gain.connect(this.masterGain);
        source.start();
        // Low boom
        const osc = this.ctx.createOscillator();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(80, this.ctx.currentTime);
        osc.frequency.exponentialRampToValueAtTime(20, this.ctx.currentTime + 0.5);
        const oscGain = this.ctx.createGain();
        oscGain.gain.setValueAtTime(0.3, this.ctx.currentTime);
        oscGain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5);
        osc.connect(oscGain);
        oscGain.connect(this.masterGain);
        osc.start();
        osc.stop(this.ctx.currentTime + 0.6);
    }
    playGameOver() {
        if (!this.ctx || !this.masterGain)
            return;
        const ctx = this.ctx;
        const mg = this.masterGain;
        const notes = [440, 370, 311, 261];
        notes.forEach((freq, i) => {
            const osc = ctx.createOscillator();
            osc.type = 'sine';
            osc.frequency.value = freq;
            const gain = ctx.createGain();
            const t = ctx.currentTime + i * 0.2;
            gain.gain.setValueAtTime(0, t);
            gain.gain.linearRampToValueAtTime(0.1, t + 0.05);
            gain.gain.exponentialRampToValueAtTime(0.001, t + 0.4);
            osc.connect(gain);
            gain.connect(mg);
            osc.start(t);
            osc.stop(t + 0.5);
        });
    }
    setVolume(v) {
        if (this.masterGain) {
            this.masterGain.gain.value = v;
        }
    }
}
exports.GameAudio = GameAudio;
};

// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParticleManager = void 0;
class ParticleManager {
    constructor(scene, config) {
        this.burstParticles = [];
        this.scene = scene;
        this.config = config;
        this.trailSystem = {
            particles: [],
            maxParticles: 200,
            emitRate: 8,
            emitTimer: 0,
        };
        this.debrisSystem = {
            particles: [],
            maxParticles: 100,
            emitRate: 3,
            emitTimer: 0,
        };
    }
    emitTrail(position, speed) {
        const system = this.trailSystem;
        system.emitTimer += speed * 0.05;
        while (system.emitTimer >= 1 && system.particles.length < system.maxParticles) {
            system.emitTimer -= 1;
            const p = this.createParticle(position, {
                vx: (Math.random() - 0.5) * 0.5,
                vy: (Math.random() - 0.5) * 0.5,
                vz: 2 + Math.random() * 2,
                life: 0.8 + Math.random() * 0.4,
                size: 0.3 + Math.random() * 0.3,
                color: speed > this.config.maxSpeed * 0.8 ? 0x44aaff : 0xff6633,
            });
            system.particles.push(p);
            this.scene.add(p.mesh);
        }
    }
    emitDebris(position) {
        const system = this.debrisSystem;
        system.emitTimer += 1;
        while (system.emitTimer >= 1 && system.particles.length < system.maxParticles) {
            system.emitTimer -= 1;
            const angle = Math.random() * Math.PI * 2;
            const dist = this.config.canyonWidth * 0.35 + Math.random() * 5;
            const p = this.createParticle(new THREE.Vector3(position.x + Math.cos(angle) * dist, position.y + (Math.random() - 0.5) * 20, position.z + Math.random() * 10), {
                vx: (Math.random() - 0.5) * 2,
                vy: (Math.random() - 0.5) * 2,
                vz: 5 + Math.random() * 10,
                life: 0.5 + Math.random() * 0.5,
                size: 0.2 + Math.random() * 0.4,
                color: 0x886655,
            });
            system.particles.push(p);
            this.scene.add(p.mesh);
        }
    }
    emitBurst(position, color, count = 15) {
        for (let i = 0; i < count; i++) {
            const p = this.createParticle(position, {
                vx: (Math.random() - 0.5) * 8,
                vy: (Math.random() - 0.5) * 8,
                vz: (Math.random() - 0.5) * 8,
                life: 0.3 + Math.random() * 0.5,
                size: 0.3 + Math.random() * 0.5,
                color: color,
            });
            this.burstParticles.push(p);
            this.scene.add(p.mesh);
        }
    }
    emitCrash(position) {
        for (let i = 0; i < 40; i++) {
            const p = this.createParticle(position, {
                vx: (Math.random() - 0.5) * 20,
                vy: (Math.random() - 0.5) * 20,
                vz: (Math.random() - 0.5) * 20,
                life: 0.5 + Math.random() * 1.0,
                size: 0.5 + Math.random() * 1.5,
                color: Math.random() > 0.5 ? 0xff4400 : 0xffaa00,
            });
            this.burstParticles.push(p);
            this.scene.add(p.mesh);
        }
    }
    createParticle(pos, opts) {
        const geo = new THREE.BoxGeometry(opts.size, opts.size, opts.size);
        const mat = new THREE.MeshBasicMaterial({
            color: opts.color,
            transparent: true,
            opacity: 0.8,
        });
        const mesh = new THREE.Mesh(geo, mat);
        mesh.position.copy(pos);
        return {
            position: pos.clone(),
            velocity: new THREE.Vector3(opts.vx, opts.vy, opts.vz),
            life: opts.life,
            maxLife: opts.life,
            size: opts.size,
            color: opts.color,
            mesh: mesh,
        };
    }
    update(dt, playerZ) {
        const allParticles = [
            ...this.trailSystem.particles,
            ...this.debrisSystem.particles,
            ...this.burstParticles,
        ];
        for (let i = allParticles.length - 1; i >= 0; i--) {
            const p = allParticles[i];
            p.life -= dt;
            if (p.life <= 0 || !p.mesh) {
                this.removeParticle(p, i, allParticles);
                continue;
            }
            // Update position
            p.position.x += p.velocity.x * dt;
            p.position.y += p.velocity.y * dt;
            p.position.z += p.velocity.z * dt;
            if (p.mesh) {
                p.mesh.position.copy(p.position);
                const alpha = Math.max(0, p.life / p.maxLife);
                p.mesh.material.opacity = alpha * 0.8;
                p.mesh.rotation.x += dt * 3;
                p.mesh.rotation.y += dt * 2;
            }
            // Remove if too far behind player
            if (p.position.z < playerZ - 50) {
                this.removeParticle(p, i, allParticles);
            }
        }
        // Clean up empty systems
        this.trailSystem.particles = this.trailSystem.particles.filter(p => p.life > 0);
        this.debrisSystem.particles = this.debrisSystem.particles.filter(p => p.life > 0);
        this.burstParticles = this.burstParticles.filter(p => p.life > 0);
    }
    removeParticle(p, idx, allParticles) {
        if (p.mesh) {
            this.scene.remove(p.mesh);
            p.mesh.geometry.dispose?.();
            if (p.mesh.material) {
                p.mesh.material.dispose?.();
            }
        }
        // Remove from the right system
        const trailIdx = this.trailSystem.particles.indexOf(p);
        if (trailIdx >= 0)
            this.trailSystem.particles.splice(trailIdx, 1);
        const debrisIdx = this.debrisSystem.particles.indexOf(p);
        if (debrisIdx >= 0)
            this.debrisSystem.particles.splice(debrisIdx, 1);
        const burstIdx = this.burstParticles.indexOf(p);
        if (burstIdx >= 0)
            this.burstParticles.splice(burstIdx, 1);
    }
    reset() {
        const all = [
            ...this.trailSystem.particles,
            ...this.debrisSystem.particles,
            ...this.burstParticles,
        ];
        for (const p of all) {
            if (p.mesh) {
                this.scene.remove(p.mesh);
                p.mesh.geometry.dispose?.();
                if (p.mesh.material)
                    p.mesh.material.dispose?.();
            }
        }
        this.trailSystem.particles = [];
        this.debrisSystem.particles = [];
        this.burstParticles = [];
    }
}
exports.ParticleManager = ParticleManager;
};

// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HUD = void 0;
class HUD {
    constructor() {
        this.container = document.getElementById('app');
        this.buildHUD();
    }
    buildHUD() {
        // Create canvas container if it doesn't exist
        if (!document.getElementById('canvas-container')) {
            const canvasContainer = document.createElement('div');
            canvasContainer.id = 'canvas-container';
            canvasContainer.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:0;';
            this.container.appendChild(canvasContainer);
        }
        // HUD overlay
        const hud = document.createElement('div');
        hud.id = 'hud';
        hud.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;z-index:10;pointer-events:none;';
        // Speed bar (left side)
        this.speedBar = document.createElement('div');
        this.speedBar.id = 'speed-bar';
        this.speedBar.style.cssText = `
      position:absolute;left:30px;top:50%;transform:translateY(-50%);
      width:6px;height:200px;background:rgba(255,255,255,0.1);border-radius:3px;overflow:hidden;
    `;
        const speedFill = document.createElement('div');
        speedFill.id = 'speed-fill';
        speedFill.style.cssText = `
      width:100%;height:50%;background:linear-gradient(to top,#ff4400,#ffaa00);
      border-radius:3px;transition:height 0.1s;position:absolute;bottom:0;
    `;
        this.speedBar.appendChild(speedFill);
        hud.appendChild(this.speedBar);
        // Speed text
        const speedText = document.createElement('div');
        speedText.id = 'speed-text';
        speedText.style.cssText = `
      position:absolute;left:45px;top:50%;transform:translateY(-50%);
      color:#ff8844;font-family:'Courier New',monospace;font-size:14px;font-weight:bold;
      text-shadow:0 0 10px rgba(255,68,0,0.5);
    `;
        speedText.textContent = '0';
        hud.appendChild(speedText);
        // Multiplier
        this.multiplierEl = document.createElement('div');
        this.multiplierEl.id = 'multiplier';
        this.multiplierEl.style.cssText = `
      position:absolute;right:30px;top:20px;
      color:#ffcc00;font-family:'Courier New',monospace;font-size:48px;font-weight:bold;
      text-shadow:0 0 20px rgba(255,204,0,0.5);opacity:0.8;transition:all 0.2s;
    `;
        this.multiplierEl.textContent = 'x1';
        hud.appendChild(this.multiplierEl);
        // Score
        this.scoreEl = document.createElement('div');
        this.scoreEl.id = 'score';
        this.scoreEl.style.cssText = `
      position:absolute;right:30px;top:80px;
      color:#ffffff;font-family:'Courier New',monospace;font-size:18px;font-weight:bold;
      text-shadow:0 0 10px rgba(255,255,255,0.3);opacity:0.7;
    `;
        this.scoreEl.textContent = '0';
        hud.appendChild(this.scoreEl);
        // Energy bar (bottom center)
        const energyContainer = document.createElement('div');
        energyContainer.id = 'energy-container';
        energyContainer.style.cssText = `
      position:absolute;bottom:30px;left:50%;transform:translateX(-50%);
      width:250px;height:8px;background:rgba(255,255,255,0.1);border-radius:4px;overflow:hidden;
    `;
        this.energyBar = document.createElement('div');
        this.energyBar.id = 'energy-bar';
        this.energyBar.style.cssText = `
      width:100%;height:100%;background:linear-gradient(to right,#ff4400,#ffaa00);
      border-radius:4px;transition:width 0.15s;
    `;
        energyContainer.appendChild(this.energyBar);
        hud.appendChild(energyContainer);
        // Energy label
        const energyLabel = document.createElement('div');
        energyLabel.id = 'energy-label';
        energyLabel.style.cssText = `
      position:absolute;bottom:45px;left:50%;transform:translateX(-50%);
      color:#ff8844;font-family:'Courier New',monospace;font-size:11px;font-weight:bold;
      text-transform:uppercase;letter-spacing:2px;opacity:0.6;
    `;
        energyLabel.textContent = 'ENERGY';
        hud.appendChild(energyLabel);
        // Turbo bar (below energy)
        const turboContainer = document.createElement('div');
        turboContainer.id = 'turbo-container';
        turboContainer.style.cssText = `
      position:absolute;bottom:15px;left:50%;transform:translateX(-50%);
      width:200px;height:5px;background:rgba(255,255,255,0.05);border-radius:3px;overflow:hidden;
    `;
        this.turboBar = document.createElement('div');
        this.turboBar.id = 'turbo-bar';
        this.turboBar.style.cssText = `
      width:0%;height:100%;background:linear-gradient(to right,#00aaff,#44ddff);
      border-radius:3px;transition:width 0.1s;
    `;
        turboContainer.appendChild(this.turboBar);
        hud.appendChild(turboContainer);
        // Near-miss flash
        this.nearMissFlash = document.createElement('div');
        this.nearMissFlash.id = 'near-miss-flash';
        this.nearMissFlash.style.cssText = `
      position:absolute;top:0;left:0;width:100%;height:100%;
      background:radial-gradient(ellipse at center,rgba(255,200,0,0.15),transparent 70%);
      opacity:0;transition:opacity 0.1s;pointer-events:none;
    `;
        hud.appendChild(this.nearMissFlash);
        this.container.appendChild(hud);
        // Game Over screen
        this.gameOverScreen = document.createElement('div');
        this.gameOverScreen.id = 'game-over';
        this.gameOverScreen.style.cssText = `
      position:fixed;top:0;left:0;width:100%;height:100%;
      background:rgba(10,5,20,0.92);z-index:100;display:none;
      flex-direction:column;align-items:center;justify-content:center;
      pointer-events:auto;
    `;
        this.gameOverScreen.innerHTML = `
      <div style="color:#ff4400;font-family:'Courier New',monospace;font-size:64px;font-weight:bold;
        text-shadow:0 0 40px rgba(255,68,0,0.5);margin-bottom:10px;">CRASHED</div>
      <div style="color:#ffffff;font-family:'Courier New',monospace;font-size:16px;opacity:0.6;margin-bottom:40px;">
        SIGNAL LOST
      </div>
      <div style="color:#ffcc00;font-family:'Courier New',monospace;font-size:42px;font-weight:bold;
        text-shadow:0 0 20px rgba(255,204,0,0.4);margin-bottom:8px;" id="final-score">0</div>
      <div style="color:#ffffff;font-family:'Courier New',monospace;font-size:14px;opacity:0.5;margin-bottom:40px;">
        FINAL SCORE
      </div>
      <div style="display:flex;gap:40px;margin-bottom:50px;">
        <div style="text-align:center;">
          <div style="color:#44ddff;font-family:'Courier New',monospace;font-size:28px;font-weight:bold;" id="final-max-speed">0</div>
          <div style="color:#ffffff;font-family:'Courier New',monospace;font-size:11px;opacity:0.4;margin-top:4px;">MAX SPEED</div>
        </div>
        <div style="text-align:center;">
          <div style="color:#ff8844;font-family:'Courier New',monospace;font-size:28px;font-weight:bold;" id="final-max-mult">x1</div>
          <div style="color:#ffffff;font-family:'Courier New',monospace;font-size:11px;opacity:0.4;margin-top:4px;">MAX MULTIPLIER</div>
        </div>
      </div>
      <button id="restart-btn" style="
        background:none;border:2px solid #ff4400;color:#ff4400;
        font-family:'Courier New',monospace;font-size:18px;font-weight:bold;
        padding:12px 40px;cursor:pointer;letter-spacing:3px;text-transform:uppercase;
        transition:all 0.2s;pointer-events:auto;
      ">RESTART</button>
    `;
        this.container.appendChild(this.gameOverScreen);
        // Start screen
        this.startScreen = document.createElement('div');
        this.startScreen.id = 'start-screen';
        this.startScreen.style.cssText = `
      position:fixed;top:0;left:0;width:100%;height:100%;
      background:rgba(10,5,20,0.95);z-index:100;
      display:flex;flex-direction:column;align-items:center;justify-content:center;
      pointer-events:auto;
    `;
        this.startScreen.innerHTML = `
      <div style="color:#ff4400;font-family:'Courier New',monospace;font-size:56px;font-weight:bold;
        text-shadow:0 0 40px rgba(255,68,0,0.5);margin-bottom:8px;letter-spacing:4px;">CANYON RUN</div>
      <div style="color:#ff8844;font-family:'Courier New',monospace;font-size:14px;opacity:0.5;margin-bottom:50px;
        letter-spacing:6px;">HIGH-SPEED PROCEDURAL RACER</div>
      <div style="color:#ffffff;font-family:'Courier New',monospace;font-size:13px;opacity:0.4;
        text-align:center;line-height:2;margin-bottom:50px;">
        WASD / ARROWS — STEER<br>
        SPACE — BOOST<br>
        COLLECT ORBS — RECHARGE ENERGY<br>
        STAY ALIVE — SURVIVE
      </div>
      <button id="start-btn" style="
        background:none;border:2px solid #ff4400;color:#ff4400;
        font-family:'Courier New',monospace;font-size:20px;font-weight:bold;
        padding:14px 50px;cursor:pointer;letter-spacing:4px;text-transform:uppercase;
        transition:all 0.2s;pointer-events:auto;
      ">LAUNCH</button>
    `;
        this.container.appendChild(this.startScreen);
        // References
        this.finalScoreEl = document.getElementById('final-score');
        this.finalMaxSpeedEl = document.getElementById('final-max-speed');
        this.finalMaxMultEl = document.getElementById('final-max-mult');
        this.restartBtn = document.getElementById('restart-btn');
    }
    updateSpeed(speed, maxSpeed) {
        const fill = document.getElementById('speed-fill');
        const pct = Math.min(1, speed / maxSpeed);
        fill.style.height = (pct * 100) + '%';
        const speedText = document.getElementById('speed-text');
        speedText.textContent = String(Math.round(speed));
    }
    updateMultiplier(multiplier, pulse) {
        this.multiplierEl.textContent = 'x' + multiplier.toFixed(1);
        if (pulse) {
            this.multiplierEl.style.transform = 'scale(1.2)';
            this.multiplierEl.style.textShadow = '0 0 30px rgba(255,204,0,0.8)';
            setTimeout(() => {
                this.multiplierEl.style.transform = 'scale(1)';
                this.multiplierEl.style.textShadow = '0 0 20px rgba(255,204,0,0.5)';
            }, 200);
        }
    }
    updateEnergy(energy, maxEnergy) {
        const pct = Math.max(0, energy / maxEnergy);
        this.energyBar.style.width = (pct * 100) + '%';
        if (pct < 0.2) {
            this.energyBar.style.background = 'linear-gradient(to right,#ff0000,#ff4400)';
        }
        else {
            this.energyBar.style.background = 'linear-gradient(to right,#ff4400,#ffaa00)';
        }
    }
    updateTurbo(turbo, maxTurbo, boosting) {
        const pct = Math.max(0, turbo / maxTurbo);
        this.turboBar.style.width = (pct * 100) + '%';
        if (boosting) {
            this.turboBar.style.background = 'linear-gradient(to right,#00ffff,#88ffff)';
            this.turboBar.style.boxShadow = '0 0 10px rgba(0,255,255,0.5)';
        }
        else {
            this.turboBar.style.background = 'linear-gradient(to right,#00aaff,#44ddff)';
            this.turboBar.style.boxShadow = 'none';
        }
    }
    updateScore(score) {
        this.scoreEl.textContent = Math.round(score).toLocaleString();
    }
    flashNearMiss() {
        this.nearMissFlash.style.opacity = '1';
        setTimeout(() => {
            this.nearMissFlash.style.opacity = '0';
        }, 150);
    }
    showGameOver(score, maxSpeed, maxMult) {
        this.gameOverScreen.style.display = 'flex';
        this.finalScoreEl.textContent = Math.round(score).toLocaleString();
        this.finalMaxSpeedEl.textContent = String(Math.round(maxSpeed));
        this.finalMaxMultEl.textContent = 'x' + maxMult.toFixed(1);
    }
    hideGameOver() {
        this.gameOverScreen.style.display = 'none';
    }
    showStartScreen() {
        this.startScreen.style.display = 'flex';
    }
    hideStartScreen() {
        this.startScreen.style.display = 'none';
    }
    onRestart(callback) {
        this.restartBtn.onclick = () => {
            callback();
        };
        // Hover effects
        this.restartBtn.onmouseenter = () => {
            this.restartBtn.style.background = 'rgba(255,68,0,0.15)';
            this.restartBtn.style.boxShadow = '0 0 20px rgba(255,68,0,0.3)';
        };
        this.restartBtn.onmouseleave = () => {
            this.restartBtn.style.background = 'none';
            this.restartBtn.style.boxShadow = 'none';
        };
    }
    onStart(callback) {
        const startBtn = document.getElementById('start-btn');
        if (startBtn) {
            startBtn.onclick = () => {
                callback();
            };
            startBtn.onmouseenter = () => {
                startBtn.style.background = 'rgba(255,68,0,0.15)';
                startBtn.style.boxShadow = '0 0 20px rgba(255,68,0,0.3)';
            };
            startBtn.onmouseleave = () => {
                startBtn.style.background = 'none';
                startBtn.style.boxShadow = 'none';
            };
        }
    }
}
exports.HUD = HUD;
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = void 0;
class Renderer {
    constructor(config, cameraState) {
        this.config = config;
        this.cameraState = cameraState;
        this.canvasContainer = document.getElementById('canvas-container');
        // Scene
        this._scene = new THREE.Scene();
        this._scene.fog = new THREE.FogExp2(0x1a0a20, 0.0015);
        // Camera
        this._camera = new THREE.PerspectiveCamera(cameraState.fov, window.innerWidth / window.innerHeight, 0.5, 2000);
        this._camera.position.set(0, cameraState.height, -cameraState.distance);
        // Renderer
        this._renderer = new THREE.WebGLRenderer({
            antialias: true,
            alpha: false,
        });
        this._renderer.setSize(window.innerWidth, window.innerHeight);
        this._renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this._renderer.setClearColor(0x1a0a20, 1);
        this._renderer.shadowMap.enabled = true;
        this._renderer.shadowMap.type = THREE.BasicShadowMap;
        this.canvasContainer.appendChild(this._renderer.domElement);
        // Sun light (directional, golden hour)
        this.sunLight = new THREE.DirectionalLight(0xffaa66, 1.8);
        this.sunLight.position.set(50, 80, -100);
        this.sunLight.castShadow = true;
        this.sunLight.shadow.mapSize.width = 1024;
        this.sunLight.shadow.mapSize.height = 1024;
        this.sunLight.shadow.camera.near = 1;
        this.sunLight.shadow.camera.far = 500;
        this.sunLight.shadow.camera.left = -100;
        this.sunLight.shadow.camera.right = 100;
        this.sunLight.shadow.camera.top = 100;
        this.sunLight.shadow.camera.bottom = -100;
        this._scene.add(this.sunLight);
        // Warm fill light from behind
        const fillLight = new THREE.DirectionalLight(0xff6633, 0.5);
        fillLight.position.set(0, 20, 50);
        this._scene.add(fillLight);
        // Ambient light (purple-ish)
        this.ambientLight = new THREE.AmbientLight(0x553377, 0.6);
        this._scene.add(this.ambientLight);
        // Hemisphere light (sky/ground)
        this.hemiLight = new THREE.HemisphereLight(0xff9966, 0x331144, 0.5);
        this._scene.add(this.hemiLight);
        // Sun glow sphere (visual sun in distance)
        const sunGeo = new THREE.SphereGeometry(30, 12, 8);
        const sunMat = new THREE.MeshBasicMaterial({
            color: 0xff6622,
            transparent: true,
            opacity: 0.7,
        });
        const sunMesh = new THREE.Mesh(sunGeo, sunMat);
        sunMesh.position.set(0, 40, -500);
        this._scene.add(sunMesh);
        // Sun glow sprite
        const sunCanvas = document.createElement('canvas');
        sunCanvas.width = 256;
        sunCanvas.height = 256;
        const sctx = sunCanvas.getContext('2d');
        if (sctx) {
            const sgrad = sctx.createRadialGradient(128, 128, 0, 128, 128, 128);
            sgrad.addColorStop(0, 'rgba(255,150,50,0.9)');
            sgrad.addColorStop(0.2, 'rgba(255,100,30,0.5)');
            sgrad.addColorStop(0.5, 'rgba(200,50,20,0.2)');
            sgrad.addColorStop(1, 'rgba(100,20,10,0)');
            sctx.fillStyle = sgrad;
            sctx.fillRect(0, 0, 256, 256);
        }
        const sunTex = new THREE.CanvasTexture(sunCanvas);
        const sunGlowMat = new THREE.SpriteMaterial({
            map: sunTex,
            transparent: true,
            blending: THREE.AdditiveBlending,
            depthWrite: false,
        });
        const sunGlow = new THREE.Sprite(sunGlowMat);
        sunGlow.scale.set(200, 200, 1);
        sunGlow.position.set(0, 40, -500);
        this._scene.add(sunGlow);
        // Handle resize
        window.addEventListener('resize', () => {
            this._camera.aspect = window.innerWidth / window.innerHeight;
            this._camera.updateMatrixWorld();
            this._renderer.setSize(window.innerWidth, window.innerHeight);
        });
    }
    get scene() { return this._scene; }
    get camera() { return this._camera; }
    get renderer() { return this._renderer; }
    updateCamera(playerPos, playerSpeed, dt) {
        // Smooth FOV transition based on speed
        const speedRatio = playerSpeed / (this.config.maxSpeed * 1.5);
        this.cameraState.targetFov = 70 + speedRatio * 25;
        this.cameraState.fov += (this.cameraState.targetFov - this.cameraState.fov) * Math.min(1, dt * 3);
        this._camera.fov = this.cameraState.fov;
        this._camera.updateMatrixWorld();
        // Camera follows player with slight lag
        const targetX = playerPos.x * 0.6;
        const targetY = playerPos.y * 0.5 + this.cameraState.height;
        const targetZ = playerPos.z - this.cameraState.distance;
        this._camera.position.x += (targetX - this._camera.position.x) * Math.min(1, dt * 5);
        this._camera.position.y += (targetY - this._camera.position.y) * Math.min(1, dt * 4);
        this._camera.position.z += (targetZ - this._camera.position.z) * Math.min(1, dt * 8);
        // Camera shake
        if (this.cameraState.shake > 0.01) {
            this._camera.position.x += (Math.random() - 0.5) * this.cameraState.shake;
            this._camera.position.y += (Math.random() - 0.5) * this.cameraState.shake;
            this.cameraState.shake *= this.cameraState.shakeDecay;
        }
        // Look ahead of player
        const lookTarget = new THREE.Vector3(playerPos.x * 0.3, playerPos.y * 0.3, playerPos.z + 80);
        this._camera.lookAt(lookTarget);
        // Update sun to follow player
        this.sunLight.position.set(playerPos.x + 50, 80, playerPos.z - 100);
    }
    triggerShake(intensity) {
        this.cameraState.shake = Math.min(this.cameraState.shake + intensity, 3);
    }
    render() {
        this._renderer.render(this._scene, this._camera);
    }
    dispose() {
        this._renderer.dispose?.();
    }
}
exports.Renderer = Renderer;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"638c8e4a-2243-4dfd-a66a-8c3dac457531","tokensIn":3582108,"tokensOut":51450,"tokensTotal":3633558,"cost":0,"turns":83,"toolCalls":82,"failedToolCalls":7,"timestamp":"2026-08-01T08:26:28.267Z"} -->