Canyon Run
Qwen 3.6 27b MTP · typescript

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 69,353 bytes · SHA-256 3d5f43518c5f
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Racer — High-Speed Procedural Arcade</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #0a0612; font-family: 'Segoe UI', system-ui, sans-serif; }
#game-container { position: relative; width: 100vw; height: 100vh; }
#game-canvas { display: block; width: 100%; height: 100%; }
.hidden { display: none !important; }
/* Vignette overlay */
#game-container::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: radial-gradient(ellipse at center, transparent 50%, rgba(10,6,18,0.6) 100%);
pointer-events: none;
z-index: 4;
}
/* Start Screen */
#start-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: radial-gradient(ellipse at center, rgba(255,100,30,0.08) 0%, rgba(10,6,18,0.97) 70%);
z-index: 10; color: #fff; text-align: center;
}
#start-screen h1 {
font-size: clamp(2rem, 6vw, 5rem); font-weight: 900; letter-spacing: 0.3em;
background: linear-gradient(180deg, #ffaa44 0%, #ff4422 50%, #cc2266 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
margin-bottom: 0.2em;
}
#start-screen .subtitle {
font-size: clamp(0.7rem, 1.5vw, 1rem); letter-spacing: 0.5em; color: #8866aa;
text-transform: uppercase; margin-bottom: 3em;
}
#controls-info { margin-bottom: 2em; }
#controls-info p { font-size: 0.9rem; color: #aaa; margin: 0.4em 0; letter-spacing: 0.1em; }
#controls-info strong { color: #ffaa44; }
/* Buttons */
button {
padding: 0.8em 3em; font-size: 1.1rem; font-weight: 700; letter-spacing: 0.2em;
border: 2px solid #ff6633; background: transparent; color: #ffaa44;
cursor: pointer; text-transform: uppercase; transition: all 0.2s;
}
button:hover { background: #ff6633; color: #fff; }
/* HUD */
#hud { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 5; }
#score-display {
position: absolute; top: 20px; right: 30px; font-size: 2rem; font-weight: 900;
color: #ffcc44; text-shadow: 0 0 20px rgba(255,170,68,0.5); letter-spacing: 0.05em;
}
#multiplier-display {
position: absolute; top: 55px; right: 30px; font-size: 1.4rem; font-weight: 700;
color: #ff6644; text-shadow: 0 0 15px rgba(255,102,68,0.4); letter-spacing: 0.1em;
transition: transform 0.1s;
}
/* Speed Bar - Left side */
#speed-bar-container {
position: absolute; left: 30px; top: 50%; transform: translateY(-50%);
width: 6px; height: 200px; background: rgba(255,255,255,0.1); border-radius: 3px;
}
#speed-bar {
position: absolute; bottom: 0; left: 0; width: 100%; height: 50%;
background: linear-gradient(0deg, #ff4422, #ffaa44); border-radius: 3px;
transition: height 0.1s; box-shadow: 0 0 10px rgba(255,68,34,0.5);
}
/* Energy Bar - Bottom center */
#energy-container {
position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%);
width: 200px; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px;
}
#energy-bar {
position: absolute; left: 0; top: 0; width: 100%; height: 100%;
background: linear-gradient(90deg, #ff2266, #ffaa44); border-radius: 4px;
transition: width 0.1s; box-shadow: 0 0 15px rgba(255,34,102,0.5);
}
/* Turbo Bar - Bottom right */
#turbo-container {
position: absolute; bottom: 30px; right: 30px;
width: 120px; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px;
}
#turbo-bar {
position: absolute; left: 0; top: 0; width: 0%; height: 100%;
background: linear-gradient(90deg, #44aaff, #88ffcc); border-radius: 3px;
transition: width 0.1s; box-shadow: 0 0 10px rgba(68,170,255,0.5);
}
/* Game Over Screen */
#game-over-screen {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: rgba(10,6,18,0.92); z-index: 10; color: #fff; text-align: center;
}
#game-over-screen h1 {
font-size: clamp(2rem, 5vw, 4rem); font-weight: 900; letter-spacing: 0.3em;
color: #ff4466; margin-bottom: 2em; text-shadow: 0 0 30px rgba(255,68,102,0.5);
}
#final-stats { display: flex; gap: 3em; margin-bottom: 2em; }
#final-stats > div { display: flex; flex-direction: column; align-items: center; }
.stat-label { font-size: 0.7rem; letter-spacing: 0.3em; color: #8866aa; text-transform: uppercase; margin-bottom: 0.3em; }
.stat-value { font-size: 2rem; font-weight: 900; color: #ffcc44; text-shadow: 0 0 15px rgba(255,204,68,0.4); }
/* Near-miss flash */
@keyframes nearMissFlash {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.3); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
.near-miss-pulse { animation: nearMissFlash 0.2s ease-out; }
/* Energy warning pulse */
@keyframes energyWarning {
0%, 100% { box-shadow: 0 0 15px rgba(255,34,102,0.5); }
50% { box-shadow: 0 0 25px rgba(255,0,68,0.9); }
}
/* Turbo ready glow */
@keyframes turboReady {
0%, 100% { box-shadow: 0 0 10px rgba(68,170,255,0.5); }
50% { box-shadow: 0 0 20px rgba(68,170,255,0.9), 0 0 30px rgba(136,255,204,0.3); }
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="hud" class="hidden">
<div id="speed-bar-container"><div id="speed-bar"></div></div>
<div id="multiplier-display">x1.0</div>
<div id="energy-container"><div id="energy-bar"></div></div>
<div id="turbo-container"><div id="turbo-bar"></div></div>
<div id="score-display">0</div>
</div>
<div id="start-screen">
<h1>CANYON RACER</h1>
<p class="subtitle">HIGH-SPEED PROCEDURAL ARCADE</p>
<div id="controls-info">
<p><strong>W/S</strong> — Pitch Up/Down</p>
<p><strong>A/D</strong> — Roll Left/Right</p>
<p><strong>SPACE</strong> — Boost</p>
</div>
<button id="start-btn">LAUNCH</button>
</div>
<div id="game-over-screen" class="hidden">
<h1>RUN ENDED</h1>
<div id="final-stats">
<div><span class="stat-label">FINAL SCORE</span><span id="final-score" class="stat-value">0</span></div>
<div><span class="stat-label">MAX SPEED</span><span id="max-speed" class="stat-value">0</span></div>
<div><span class="stat-label">DISTANCE</span><span id="distance" class="stat-value">0</span></div>
</div>
<button id="restart-btn">RESTART</button>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./types":"src/types.ts","./state":"src/state.ts","./audio":"src/audio.ts","./player":"src/player.ts","./world":"src/world.ts","./particles":"src/particles.ts","./hud":"src/hud.ts","./postprocessing":"src/postprocessing.ts"},"src/state.ts":{"./types":"src/types.ts"},"src/player.ts":{"./types":"src/types.ts"},"src/world.ts":{"./types":"src/types.ts"},"src/hud.ts":{"./types":"src/types.ts"},"src/postprocessing.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";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
const state_1 = require("./state");
const audio = __importStar(require("./audio"));
const playerModule = __importStar(require("./player"));
const world = __importStar(require("./world"));
const particles = __importStar(require("./particles"));
const hud = __importStar(require("./hud"));
const postprocessing = __importStar(require("./postprocessing"));
// Three.js globals
let scene;
let camera;
let renderer;
let playerCraft;
// Game state
let playerState;
let gameState;
// Track which chunk meshes are in the scene for proper cleanup
const activeChunkKeys = new Set();
// Add distant sky particles for depth perception
function addSkyParticles(scene) {
const starCount = 200;
const starGeo = new THREE.BoxGeometry(0.1, 0.1, 0.1);
const starMat = new THREE.MeshBasicMaterial({
color: 0xffaacc, transparent: true, opacity: 0.4
});
for (let i = 0; i < starCount; i++) {
const star = new THREE.Mesh(starGeo, starMat);
star.position.set((Math.random() - 0.5) * 200, Math.random() * 80 + 10, -Math.random() * 400 - 50);
const s = 0.5 + Math.random();
star.scale.set(s, s, s);
scene.add(star);
}
}
// Timing
let lastTime = 0;
let frameCount = 0;
let debrisTimer = 0;
let trailTimer = 0;
// Camera target for smooth following
let cameraTargetPos;
let cameraCurrentOffset;
// Near-miss tracking
let lastNearMissTime = 0;
function init() {
// Setup Three.js scene with sunset gradient feel
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a0830);
scene.fog = new THREE.Fog(0x1a0830, 50, 200);
// Camera
camera = new THREE.PerspectiveCamera(types_1.CONFIG.FOV_BASE, window.innerWidth / window.innerHeight, 0.1, 500);
cameraTargetPos = new THREE.Vector3(0, types_1.CONFIG.CAMERA_HEIGHT, types_1.CONFIG.CAMERA_DISTANCE);
cameraCurrentOffset = new THREE.Vector3();
// Renderer
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('game-canvas'),
antialias: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x1a0a2e);
// Post-processing (bloom)
postprocessing.initPostProcessing(renderer, scene, camera);
// Lighting - Golden hour / cyber sunset palette
const ambientLight = new THREE.AmbientLight(0x332244, 0.5);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xff8844, 1.0);
sunLight.position.set(-20, 30, -50);
scene.add(sunLight);
// Secondary fill light from behind
const fillLight = new THREE.DirectionalLight(0x6644aa, 0.3);
fillLight.position.set(10, 10, 20);
scene.add(fillLight);
// Hemisphere for sky/ground color variation
const hemiLight = new THREE.HemisphereLight(0xff8855, 0x332244, 0.2);
scene.add(hemiLight);
// Sun disc - glowing sphere in the distance
const sunGeo = new THREE.SphereGeometry(15, 16, 16);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xff8844 });
const sunMesh = new THREE.Mesh(sunGeo, sunMat);
sunMesh.position.set(-30, 25, -300);
scene.add(sunMesh);
// Sun glow ring
const sunGlowGeo = new THREE.SphereGeometry(22, 16, 16);
const sunGlowMat = new THREE.MeshBasicMaterial({
color: 0xff4422, transparent: true, opacity: 0.15
});
const sunGlowMesh = new THREE.Mesh(sunGlowGeo, sunGlowMat);
sunGlowMesh.position.copy(sunMesh.position);
scene.add(sunGlowMesh);
// Distant stars / particles in the sky
addSkyParticles(scene);
// Horizon glow - a large flat plane at the horizon with gradient-like appearance
const horizonGeo = new THREE.PlaneGeometry(400, 30);
const horizonMat = new THREE.MeshBasicMaterial({
color: 0xff6622, transparent: true, opacity: 0.15, side: THREE.DoubleSide
});
const horizonMesh = new THREE.Mesh(horizonGeo, horizonMat);
horizonMesh.position.set(0, 8, -300);
scene.add(horizonMesh);
// Second layer of horizon glow (wider, more transparent)
const horizon2Geo = new THREE.PlaneGeometry(500, 40);
const horizon2Mat = new THREE.MeshBasicMaterial({
color: 0xcc3366, transparent: true, opacity: 0.08, side: THREE.DoubleSide
});
const horizon2Mesh = new THREE.Mesh(horizon2Geo, horizon2Mat);
horizon2Mesh.position.set(0, 10, -350);
scene.add(horizon2Mesh);
// Create player craft
playerCraft = playerModule.createPlayerCraft();
playerModule.setPlayerMesh(playerCraft);
scene.add(playerCraft);
// Initialize game systems
playerState = (0, state_1.createPlayer)();
gameState = (0, state_1.createGameState)();
world.initWorld();
particles.initParticles(scene);
hud.initHUD();
hud.showStartScreen();
// Input handling
setupInput();
// Resize handler
window.addEventListener('resize', onResize);
// Register game callbacks for HUD buttons
window.onStartGame = startGame;
window.onRestartGame = restartGame;
// Start render loop
requestAnimationFrame(gameLoop);
}
function setupInput() {
document.addEventListener('keydown', (e) => {
switch (e.code) {
case 'KeyW':
case 'ArrowUp':
state_1.input.up = true;
break;
case 'KeyS':
case 'ArrowDown':
state_1.input.down = true;
break;
case 'KeyA':
case 'ArrowLeft':
state_1.input.left = true;
break;
case 'KeyD':
case 'ArrowRight':
state_1.input.right = true;
break;
case 'Space':
state_1.input.boost = true;
e.preventDefault();
break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.code) {
case 'KeyW':
case 'ArrowUp':
state_1.input.up = false;
break;
case 'KeyS':
case 'ArrowDown':
state_1.input.down = false;
break;
case 'KeyA':
case 'ArrowLeft':
state_1.input.left = false;
break;
case 'KeyD':
case 'ArrowRight':
state_1.input.right = false;
break;
case 'Space':
state_1.input.boost = false;
break;
}
});
}
// Sync chunk meshes with Three.js scene - add new, remove old
function syncChunkMeshes() {
const currentKeys = world.getChunks();
// Add new chunks to scene
for (const [key] of currentKeys) {
if (!activeChunkKeys.has(key)) {
activeChunkKeys.add(key);
const chunkData = currentKeys.get(key);
for (const mesh of chunkData.meshes) {
scene.add(mesh);
}
// Add orb meshes too
for (const orb of chunkData.orbs) {
if (!orb.collected && orb.mesh) {
scene.add(orb.mesh);
}
}
}
}
// Remove old chunks from scene
for (const key of activeChunkKeys) {
if (!currentKeys.has(key)) {
activeChunkKeys.delete(key);
// Meshes are cleaned up by world.cleanupChunk
}
}
}
function startGame() {
audio.initAudio();
(0, state_1.resetGame)(playerState, gameState);
particles.resetParticles();
postprocessing.resetCameraShake();
// Add initial chunk meshes to scene
activeChunkKeys.clear();
for (const [key] of world.getChunks()) {
activeChunkKeys.add(key);
const chunkData = world.getChunks().get(key);
for (const mesh of chunkData.meshes) {
scene.add(mesh);
}
for (const orb of chunkData.orbs) {
if (!orb.collected && orb.mesh) {
scene.add(orb.mesh);
}
}
}
hud.showHUD();
}
function restartGame() {
// Remove all existing meshes from scene
for (const key of activeChunkKeys) {
const chunkData = world.getChunks().get(key);
if (chunkData) {
for (const mesh of chunkData.meshes) {
scene.remove(mesh);
}
for (const orb of chunkData.orbs) {
if (orb.mesh)
scene.remove(orb.mesh);
}
}
}
// Clean up old world chunks
for (const [, chunk] of world.getChunks()) {
world.cleanupChunk(chunk);
}
(0, state_1.resetGame)(playerState, gameState);
particles.resetParticles();
postprocessing.resetCameraShake();
activeChunkKeys.clear();
world.initWorld();
// Add new initial chunks to scene
for (const [key] of world.getChunks()) {
activeChunkKeys.add(key);
const chunkData = world.getChunks().get(key);
for (const mesh of chunkData.meshes) {
scene.add(mesh);
}
for (const orb of chunkData.orbs) {
if (!orb.collected && orb.mesh) {
scene.add(orb.mesh);
}
}
}
hud.showHUD();
}
function gameLoop(timestamp) {
const dt = Math.min((timestamp - lastTime) / 1000, 0.05); // Cap delta time
lastTime = timestamp;
frameCount++;
if (gameState.running && !gameState.gameOver) {
update(dt);
}
render();
requestAnimationFrame(gameLoop);
}
function update(dt) {
// Update player physics
playerModule.updatePlayerPhysics(playerState, state_1.input, dt);
// Handle boost/turbo
handleBoost(dt);
// Update world generation and sync meshes with scene
world.updateWorld(playerState.position.z);
syncChunkMeshes();
// Collision detection & near-miss scoring
checkCollisions();
// Collect orbs
collectOrbs();
// Energy decay
playerState.energy -= types_1.CONFIG.ENERGY_DECAY * dt * 60;
if (playerState.energy <= 0) {
playerState.energy = 0;
triggerGameOver();
return;
}
// Score accumulation
gameState.score += types_1.CONFIG.BASE_SCORE_RATE * gameState.multiplier * dt;
gameState.distance -= playerState.position.z;
// Track max speed
const displaySpeed = playerState.speed * 100;
if (displaySpeed > gameState.maxSpeed) {
gameState.maxSpeed = displaySpeed;
}
// Difficulty scaling
gameState.difficulty = 1.0 + Math.floor(gameState.score / 500) * 0.1;
// Multiplier decay
if (frameCount % 3 === 0) {
gameState.multiplier = Math.max(1.0, gameState.multiplier - types_1.CONFIG.MULTIPLIER_DECAY);
}
// Update particles
particles.updateParticles(dt);
// Emit trail particles
trailTimer += dt;
if (trailTimer > 0.02) {
particles.emitTrail(playerState.position, playerState.speed);
trailTimer = 0;
}
// Emit debris particles based on speed
debrisTimer += dt;
const debrisInterval = Math.max(0.03, 0.1 - playerState.speed * 0.02);
if (debrisTimer > debrisInterval) {
particles.emitDebris(playerState.position, playerState.speed);
debrisTimer = 0;
}
// Emit speed lines at higher speeds for velocity feel
if (playerState.speed > types_1.CONFIG.BASE_SPEED * 1.2) {
const speedLineChance = (playerState.speed - types_1.CONFIG.BASE_SPEED) / (types_1.CONFIG.BOOST_SPEED - types_1.CONFIG.BASE_SPEED);
if (Math.random() < speedLineChance * 0.5) {
particles.emitSpeedLines(playerState.position, playerState.speed);
}
}
// Update audio
audio.updateEngineSound(playerState.speed, types_1.CONFIG.BOOST_SPEED);
// Update HUD
hud.updateHUD(playerState, gameState);
// Update bloom based on speed/boost
postprocessing.updateBloom(playerState.speed, playerState.isBoosting);
}
function handleBoost(dt) {
if (state_1.input.boost && playerState.turbo >= types_1.CONFIG.TURBO_MAX && !playerState.isBoosting) {
// Activate boost
playerState.isBoosting = true;
audio.playBoost();
postprocessing.triggerCameraShake(0.3);
// FOV widening effect
camera.fov = THREE.MathUtils.lerp(camera.fov, types_1.CONFIG.FOV_BOOST, 0.1);
camera.updateProjectionMatrix();
}
else if (playerState.isBoosting) {
playerState.turbo -= types_1.CONFIG.TURBO_DRAIN_RATE * dt * 60;
if (playerState.turbo <= 0) {
playerState.turbo = 0;
playerState.isBoosting = false;
// Return FOV to normal
camera.fov = THREE.MathUtils.lerp(camera.fov, types_1.CONFIG.FOV_BASE, 0.1);
camera.updateProjectionMatrix();
}
}
else {
// Build turbo during normal play
playerState.turbo += types_1.CONFIG.TURBO_BUILD_RATE * dt * 60;
playerState.turbo = Math.min(types_1.CONFIG.TURBO_MAX, playerState.turbo);
// Smooth FOV return
camera.fov = THREE.MathUtils.lerp(camera.fov, types_1.CONFIG.FOV_BASE, 0.05);
camera.updateProjectionMatrix();
}
}
function checkCollisions() {
const obstacles = world.getAllObstacles();
const playerPos = playerState.position;
const playerRadius = types_1.CONFIG.PLAYER_RADIUS;
for (const obstacle of obstacles) {
// Skip if too far away in Z
const dz = Math.abs(obstacle.position.z - playerPos.z);
if (dz > 20)
continue;
const dx = Math.abs(obstacle.position.x - playerPos.x);
const dy = Math.abs(obstacle.position.y - playerPos.y);
// Simple box-based collision check
const distX = obstacle.radius + playerRadius;
const distY = obstacle.radius * 0.8 + playerRadius;
if (dx < distX && dy < distY) {
// Collision!
triggerGameOver();
return;
}
// Near-miss detection
const totalDist = Math.sqrt(dx * dx + dy * dy);
const nearMissThreshold = types_1.CONFIG.NEAR_MISS_DISTANCE + obstacle.radius * 0.5;
if (totalDist < nearMissThreshold && dz < 8) {
const now = performance.now();
if (now - lastNearMissTime > 300) { // Debounce near-misses
gameState.multiplier += types_1.CONFIG.NEAR_MISS_MULTIPLIER_BOOST;
lastNearMissTime = now;
audio.playNearMiss();
postprocessing.triggerCameraShake(0.15);
particles.emitSparks(playerPos);
}
}
}
}
function collectOrbs() {
const orbs = world.getAllOrbs();
const playerPos = playerState.position;
for (const orb of orbs) {
if (orb.collected)
continue;
const dx = orb.position.x - playerPos.x;
const dy = orb.position.y - playerPos.y;
const dz = orb.position.z - playerPos.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < 3.0) {
// Collect!
orb.collected = true;
if (orb.mesh) {
scene.remove(orb.mesh);
// Remove children too
while (orb.mesh.children.length > 0) {
const child = orb.mesh.children[0];
orb.mesh.remove(child);
}
}
playerState.energy = Math.min(types_1.CONFIG.ENERGY_MAX, playerState.energy + types_1.CONFIG.ENERGY_RECHARGE);
audio.playCollect();
// Bonus turbo on collect
playerState.turbo = Math.min(types_1.CONFIG.TURBO_MAX, playerState.turbo + 10);
}
}
}
function triggerGameOver() {
gameState.gameOver = true;
gameState.running = false;
audio.playCrash();
postprocessing.triggerCameraShake(0.8);
hud.showGameOver(gameState);
}
function render() {
// Apply player transform to craft mesh
playerModule.applyPlayerTransform(playerState, playerCraft);
// Update engine glow
playerModule.updateEngineGlow(playerState.speed, playerState.isBoosting);
// Camera follow with smooth interpolation
const targetCamPos = new THREE.Vector3(playerState.position.x * 0.5 + cameraTargetPos.x, playerState.position.y * 0.4 + cameraTargetPos.y, playerState.position.z + types_1.CONFIG.CAMERA_DISTANCE);
// Add camera shake
const shake = postprocessing.getCameraShake();
targetCamPos.add(shake);
camera.position.lerp(targetCamPos, 0.08);
camera.lookAt(playerState.position.x * 0.3, playerState.position.y * 0.5 + 2, playerState.position.z - 30);
// Animate orbs (rotation and pulsing)
const time = performance.now() / 1000;
for (const orb of world.getAllOrbs()) {
if (!orb.collected && orb.mesh) {
orb.mesh.rotation.y += 0.03;
orb.mesh.rotation.x += 0.02;
const pulse = 1 + Math.sin(time * 4) * 0.15;
orb.mesh.scale.set(pulse, pulse, pulse);
// Update glow child
if (orb.mesh.children.length > 0) {
const glow = orb.mesh.children[0];
glow.rotation.y -= 0.02;
const glowPulse = 1 + Math.sin(time * 3 + 1) * 0.2;
glow.scale.set(glowPulse, glowPulse, glowPulse);
}
}
}
// Render particles
particles.renderParticles();
// Render scene directly (bloom achieved via emissive materials)
renderer.render(scene, camera);
}
function onResize() {
const w = window.innerWidth;
const h = window.innerHeight;
camera.aspect = w / h;
camera.updateProjectionMatrix();
renderer.setSize(w, h);
postprocessing.resizePostProcessing(w, h);
}
// Start the game
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";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CONFIG = void 0;
// Game configuration constants
exports.CONFIG = {
// Player physics
BASE_SPEED: 0.8,
MAX_SPEED: 2.5,
BOOST_SPEED: 4.0,
ACCELERATION: 0.015,
DECELERATION: 0.008,
TURN_RATE: 0.035,
BANK_ANGLE: 0.6,
PITCH_LIMIT: 0.7,
// Energy system
ENERGY_MAX: 100,
ENERGY_DECAY: 0.04,
ENERGY_RECHARGE: 25,
// Turbo system
TURBO_MAX: 100,
TURBO_BUILD_RATE: 0.15,
TURBO_DRAIN_RATE: 0.6,
TURBO_DURATION: 3000,
// Scoring
NEAR_MISS_DISTANCE: 4.0,
NEAR_MISS_MULTIPLIER_BOOST: 0.5,
MULTIPLIER_DECAY: 0.01,
BASE_SCORE_RATE: 10,
// World generation
CHUNK_LENGTH: 80,
CHUNKS_AHEAD: 6,
CHUNKS_BEHIND: 2,
CANYON_WIDTH_BASE: 30,
CANYON_HEIGHT_BASE: 40,
// Camera
CAMERA_DISTANCE: 12,
CAMERA_HEIGHT: 5,
FOV_BASE: 70,
FOV_BOOST: 95,
// Collision
PLAYER_RADIUS: 1.2,
};
};
// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.input = void 0;
exports.createPlayer = createPlayer;
exports.createGameState = createGameState;
exports.resetGame = resetGame;
const types_1 = require("./types");
exports.input = { up: false, down: false, left: false, right: false, boost: false };
function createPlayer() {
return {
position: new THREE.Vector3(0, 5, 0),
velocity: new THREE.Vector3(0, 0, types_1.CONFIG.BASE_SPEED),
rotation: new THREE.Euler(0, Math.PI, 0), // facing forward (negative Z)
speed: types_1.CONFIG.BASE_SPEED,
energy: types_1.CONFIG.ENERGY_MAX,
turbo: 0,
isBoosting: false,
};
}
function createGameState() {
return {
score: 0,
multiplier: 1.0,
maxSpeed: 0,
distance: 0,
difficulty: 1.0,
running: false,
gameOver: false,
};
}
function resetGame(player, state) {
player.position.set(0, 5, 0);
player.velocity.set(0, 0, types_1.CONFIG.BASE_SPEED);
player.rotation.set(0, Math.PI, 0);
player.speed = types_1.CONFIG.BASE_SPEED;
player.energy = types_1.CONFIG.ENERGY_MAX;
player.turbo = 0;
player.isBoosting = false;
state.score = 0;
state.multiplier = 1.0;
state.maxSpeed = 0;
state.distance = 0;
state.difficulty = 1.0;
state.running = true;
state.gameOver = false;
}
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initAudio = initAudio;
exports.updateEngineSound = updateEngineSound;
exports.playCollect = playCollect;
exports.playNearMiss = playNearMiss;
exports.playBoost = playBoost;
exports.playCrash = playCrash;
exports.stopEngine = stopEngine;
// Procedural audio system using Web Audio API
let audioCtx = null;
let engineOsc = null;
let engineGain = null;
let masterGain = null;
function initAudio() {
if (audioCtx)
return;
audioCtx = new AudioContext();
masterGain = audioCtx.createGain();
masterGain.gain.value = 0.3;
masterGain.connect(audioCtx.destination);
// Engine hum - low frequency oscillator
engineOsc = audioCtx.createOscillator();
engineGain = audioCtx.createGain();
engineOsc.type = 'sawtooth';
engineOsc.frequency.value = 60;
engineGain.gain.value = 0.08;
// Low-pass filter for engine sound
const filter = audioCtx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 200;
filter.Q.value = 5;
engineOsc.connect(filter);
filter.connect(engineGain);
engineGain.connect(masterGain);
engineOsc.start();
}
function updateEngineSound(speed, maxSpeed) {
if (!engineOsc || !engineGain)
return;
const ratio = speed / maxSpeed;
engineOsc.frequency.value = 50 + ratio * 120;
engineGain.gain.value = 0.04 + ratio * 0.1;
}
function playCollect() {
if (!audioCtx || !masterGain)
return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1200, audioCtx.currentTime + 0.1);
gain.gain.setValueAtTime(0.15, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.2);
osc.connect(gain);
gain.connect(masterGain);
osc.start();
osc.stop(audioCtx.currentTime + 0.25);
}
function playNearMiss() {
if (!audioCtx || !masterGain)
return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(300, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, audioCtx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.3);
osc.connect(gain);
gain.connect(masterGain);
osc.start();
osc.stop(audioCtx.currentTime + 0.35);
// Whoosh noise
const bufferSize = audioCtx.sampleRate * 0.2;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.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 noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const noiseGain = audioCtx.createGain();
noiseGain.gain.setValueAtTime(0.1, audioCtx.currentTime);
noiseGain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.2);
const filter = audioCtx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 800;
filter.Q.value = 2;
noise.connect(filter);
filter.connect(noiseGain);
noiseGain.connect(masterGain);
noise.start();
}
function playBoost() {
if (!audioCtx || !masterGain)
return;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(100, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(400, audioCtx.currentTime + 0.3);
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
osc.connect(gain);
gain.connect(masterGain);
osc.start();
osc.stop(audioCtx.currentTime + 0.55);
}
function playCrash() {
if (!audioCtx || !masterGain)
return;
// Noise burst for crash
const bufferSize = audioCtx.sampleRate * 0.5;
const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.4));
}
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const gain = audioCtx.createGain();
gain.gain.setValueAtTime(0.3, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
noise.connect(gain);
gain.connect(masterGain);
noise.start();
// Low thud
const osc = audioCtx.createOscillator();
const oscGain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(80, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(20, audioCtx.currentTime + 0.3);
oscGain.gain.setValueAtTime(0.3, audioCtx.currentTime);
oscGain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.4);
osc.connect(oscGain);
oscGain.connect(masterGain);
osc.start();
osc.stop(audioCtx.currentTime + 0.5);
}
function stopEngine() {
if (engineOsc) {
engineOsc.stop();
engineOsc = null;
}
}
};
// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayerCraft = createPlayerCraft;
exports.getPlayerMesh = getPlayerMesh;
exports.setPlayerMesh = setPlayerMesh;
exports.updateEngineGlow = updateEngineGlow;
exports.updatePlayerPhysics = updatePlayerPhysics;
exports.applyPlayerTransform = applyPlayerTransform;
const types_1 = require("./types");
let mesh = null;
let engineGlowMesh = null;
let engineCoreMesh = null;
function createPlayerCraft() {
const group = new THREE.Group();
// Main body - sleek arrow/dart shape
const bodyGeo = new THREE.ConeGeometry(0.5, 3.2, 6);
bodyGeo.rotateX(Math.PI / 2);
const bodyMat = new THREE.MeshPhongMaterial({
color: 0xccccdd, flatShading: true, shininess: 100,
emissive: 0x111133, emissiveIntensity: 0.2
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
group.add(body);
// Nose cone accent
const noseGeo = new THREE.ConeGeometry(0.25, 1.0, 6);
noseGeo.rotateX(Math.PI / 2);
const noseMat = new THREE.MeshPhongMaterial({
color: 0xffaa44, flatShading: true, emissive: 0xff6622, emissiveIntensity: 0.5
});
const nose = new THREE.Mesh(noseGeo, noseMat);
nose.position.z = -1.8;
group.add(nose);
// Wings - swept back triangles using custom geometry
const wingShape = createWingGeometry();
const wingMat = new THREE.MeshPhongMaterial({
color: 0x445566, flatShading: true, side: THREE.DoubleSide,
emissive: 0x112233, emissiveIntensity: 0.3
});
const leftWing = new THREE.Mesh(wingShape, wingMat);
leftWing.position.set(-1.4, -0.2, 0.5);
leftWing.rotation.z = 0.15;
group.add(leftWing);
const rightWing = new THREE.Mesh(wingShape.clone(), wingMat);
rightWing.position.set(1.4, -0.2, 0.5);
rightWing.rotation.z = -0.15;
group.add(rightWing);
// Wing tips with accent color
const tipGeo = new THREE.BoxGeometry(0.3, 0.1, 0.8);
const tipMat = new THREE.MeshPhongMaterial({
color: 0xff4422, flatShading: true, emissive: 0xff2200, emissiveIntensity: 0.5
});
const leftTip = new THREE.Mesh(tipGeo, tipMat);
leftTip.position.set(-2.3, -0.3, 1.0);
group.add(leftTip);
const rightTip = new THREE.Mesh(tipGeo.clone(), tipMat);
rightTip.position.set(2.3, -0.3, 1.0);
group.add(rightTip);
// Engine glow - outer shell
const engineGeo = new THREE.ConeGeometry(0.4, 1.5, 6);
engineGeo.rotateX(-Math.PI / 2);
const engineMat = new THREE.MeshBasicMaterial({
color: 0xff4422, transparent: true, opacity: 0.7
});
engineGlowMesh = new THREE.Mesh(engineGeo, engineMat);
engineGlowMesh.position.z = 1.8;
group.add(engineGlowMesh);
// Engine core - bright white center
const coreGeo = new THREE.ConeGeometry(0.2, 0.8, 6);
coreGeo.rotateX(-Math.PI / 2);
const coreMat = new THREE.MeshBasicMaterial({
color: 0xffffff, transparent: true, opacity: 0.9
});
engineCoreMesh = new THREE.Mesh(coreGeo, coreMat);
engineCoreMesh.position.z = 1.6;
group.add(engineCoreMesh);
// Cockpit - small diamond shape on top
const cockpitGeo = new THREE.OctahedronGeometry(0.25, 0);
const cockpitMat = new THREE.MeshPhongMaterial({
color: 0xffaa44, flatShading: true, emissive: 0xff6622, emissiveIntensity: 0.8
});
const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
cockpit.position.set(0, 0.35, -0.6);
group.add(cockpit);
return group;
}
function createWingGeometry() {
// Create a swept wing shape using PlaneGeometry with modified vertices
const geo = new THREE.PlaneGeometry(2.8, 1.4);
const pos = geo.attributes.position.array;
// Sweep back the outer edges for aerodynamic look
// Vertices are: bottom-left, top-left, bottom-right, top-right
pos[0] = -1.3;
pos[1] = 0;
pos[2] = 0.6; // bottom-left swept back
pos[3] = -1.3;
pos[4] = 0;
pos[5] = 0.6; // top-left swept back
return geo;
}
function getPlayerMesh() {
return mesh;
}
function setPlayerMesh(m) {
mesh = m;
}
function updateEngineGlow(speed, boosting) {
if (!engineGlowMesh || !engineCoreMesh)
return;
const ratio = speed / types_1.CONFIG.BOOST_SPEED;
// Scale engine based on speed
const scaleX = 1 + ratio * 0.3;
const scaleY = 1 + ratio * 2.5;
engineGlowMesh.scale.set(scaleX, scaleY, 1);
engineCoreMesh.scale.set(scaleX * 0.8, scaleY * 0.7, 1);
if (boosting) {
// Blue-white boost flame
engineGlowMesh.material.color.setHex(0x44ccff);
engineGlowMesh.material.opacity = 0.9;
engineCoreMesh.material.color.setHex(0xaaddff);
engineGlowMesh.scale.y *= 1.5;
}
else {
// Orange-red normal flame, shifts with speed
const r = Math.floor(255 * ratio + 180 * (1 - ratio));
const g = Math.floor(68 * ratio + 34 * (1 - ratio));
const b = Math.floor(34);
engineGlowMesh.material.color.setHex((r << 16) | (g << 8) | b);
engineGlowMesh.material.opacity = 0.5 + ratio * 0.4;
engineCoreMesh.material.color.setHex(0xffddaa);
}
// Flicker effect
const flicker = 1 + Math.sin(performance.now() * 0.02) * 0.1 * ratio;
engineGlowMesh.scale.y *= flicker;
}
function updatePlayerPhysics(player, inputState, dt) {
// Speed management
const targetSpeed = player.isBoosting ? types_1.CONFIG.BOOST_SPEED : types_1.CONFIG.MAX_SPEED;
if (player.speed < targetSpeed) {
player.speed += types_1.CONFIG.ACCELERATION * dt * 60;
}
else if (!player.isBoosting && player.speed > types_1.CONFIG.BASE_SPEED) {
player.speed -= types_1.CONFIG.DECELERATION * dt * 30;
}
// Clamp speed
player.speed = Math.max(types_1.CONFIG.BASE_SPEED, Math.min(player.speed, types_1.CONFIG.BOOST_SPEED));
const moveSpeed = player.speed * dt * 60;
// Lateral movement with momentum feel
if (inputState.left) {
player.velocity.x -= types_1.CONFIG.TURN_RATE * dt * 60;
}
else if (inputState.right) {
player.velocity.x += types_1.CONFIG.TURN_RATE * dt * 60;
}
// Vertical movement with momentum feel
if (inputState.up) {
player.velocity.y += types_1.CONFIG.TURN_RATE * dt * 45;
}
else if (inputState.down) {
player.velocity.y -= types_1.CONFIG.TURN_RATE * dt * 45;
}
// Dampen lateral/vertical velocity for weighty feel
const dampFactor = Math.pow(0.92, dt * 60);
player.velocity.x *= dampFactor;
player.velocity.y *= dampFactor;
// Apply movement
player.position.x += player.velocity.x * moveSpeed * 0.15;
player.position.y += player.velocity.y * moveSpeed * 0.12;
player.position.z -= moveSpeed;
// Clamp position to reasonable bounds
player.position.x = Math.max(-35, Math.min(35, player.position.x));
player.position.y = Math.max(2, Math.min(45, player.position.y));
// Banking (roll) based on lateral input/velocity
const targetRoll = -player.velocity.x * types_1.CONFIG.BANK_ANGLE;
const currentRoll = player.rotation.z;
player.rotation.z += (targetRoll - currentRoll) * 0.1 * dt * 60;
// Pitch based on vertical velocity
const targetPitch = -player.velocity.y * 0.3;
const clampedPitch = Math.max(-types_1.CONFIG.PITCH_LIMIT, Math.min(types_1.CONFIG.PITCH_LIMIT, targetPitch));
player.rotation.x += (clampedPitch - player.rotation.x) * 0.1 * dt * 60;
// Yaw stays at PI (facing forward in negative Z)
player.rotation.y = Math.PI;
}
function applyPlayerTransform(player, group) {
group.position.copy(player.position);
group.rotation.copy(player.rotation);
}
};
// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initWorld = initWorld;
exports.getChunks = getChunks;
exports.updateWorld = updateWorld;
exports.cleanupChunk = cleanupChunk;
exports.getObstaclesInRange = getObstaclesInRange;
exports.getOrbsInRange = getOrbsInRange;
exports.getAllMeshes = getAllMeshes;
exports.getAllObstacles = getAllObstacles;
exports.getAllOrbs = getAllOrbs;
const types_1 = require("./types");
const chunks = new Map();
let nextChunkZ;
// Seeded random for reproducible generation
function seededRandom(seed) {
let s = seed;
return () => {
s = (s * 16807 + 0) % 2147483647;
return (s - 1) / 2147483646;
};
}
// Color palette for canyon walls - golden hour / cyber sunset
const WALL_COLORS = [
0x994422, // deep orange-red
0xaa5533, // warm terracotta
0x884466, // purple-brown
0x773355, // dark plum
0x6644aa, // violet
0xbb6644, // burnt orange
0x995577, // mauve
];
function initWorld() {
nextChunkZ = types_1.CONFIG.CHUNK_LENGTH * types_1.CONFIG.CHUNKS_AHEAD;
for (let i = 0; i < types_1.CONFIG.CHUNKS_AHEAD + types_1.CONFIG.CHUNKS_BEHIND; i++) {
generateChunk(-i * types_1.CONFIG.CHUNK_LENGTH);
}
}
function getChunks() {
return chunks;
}
function updateWorld(playerZ) {
while (nextChunkZ > playerZ - types_1.CONFIG.CHUNK_LENGTH * types_1.CONFIG.CHUNKS_AHEAD) {
generateChunk(nextChunkZ);
nextChunkZ -= types_1.CONFIG.CHUNK_LENGTH;
}
const removeThreshold = playerZ + types_1.CONFIG.CHUNK_LENGTH * types_1.CONFIG.CHUNKS_BEHIND;
for (const [key, chunk] of chunks) {
if (chunk.zStart > -removeThreshold)
continue;
cleanupChunk(chunk);
chunks.delete(key);
}
}
function generateChunk(zPos) {
const rng = seededRandom(Math.abs(zPos * 7919 + 104729));
const chunkLength = types_1.CONFIG.CHUNK_LENGTH;
const zStart = -zPos;
const zEnd = zStart - chunkLength;
const obstacles = [];
const orbs = [];
const meshes = [];
const canyonWidth = types_1.CONFIG.CANYON_WIDTH_BASE * (1 + Math.sin(zStart * 0.02) * 0.3);
const canyonHeight = types_1.CONFIG.CANYON_HEIGHT_BASE;
// Ground plane for this chunk
addGroundPlane(meshes, zStart, zEnd, canyonWidth, rng);
// Canyon walls
generateCanyonWalls(meshes, obstacles, zStart, zEnd, canyonWidth, canyonHeight, rng);
// Floating monoliths
const monolithCount = Math.floor(2 + rng() * 3);
for (let i = 0; i < monolithCount; i++) {
generateMonolith(meshes, obstacles, zStart, zEnd, canyonWidth, canyonHeight, rng);
}
// Tight squeezes
if (rng() > 0.4) {
generateSqueeze(meshes, obstacles, zStart, zEnd, canyonWidth, canyonHeight, rng);
}
// Energy orbs - place in interesting positions
const orbCount = Math.floor(3 + rng() * 4);
for (let i = 0; i < orbCount; i++) {
generateOrb(orbs, zStart, zEnd, canyonWidth, canyonHeight, rng);
}
chunks.set(zPos, { zStart, zEnd, meshes, obstacles, orbs });
}
function addGroundPlane(meshes, zStart, zEnd, width, rng) {
// Create a ground plane with grid-like appearance using multiple segments
const segCount = Math.floor((zStart - zEnd) / 20);
for (let i = 0; i < segCount; i++) {
const segZ = zStart - (i / segCount) * (zStart - zEnd);
// Main ground strip
const geo = new THREE.PlaneGeometry(width + 20, 20);
geo.rotateX(-Math.PI / 2);
const mat = new THREE.MeshPhongMaterial({
color: 0x2a1530, flatShading: true, shininess: 5
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(0, -5, segZ);
meshes.push(mesh);
// Occasional raised platform/rock on ground
if (rng() > 0.6) {
const rockGeo = new THREE.BoxGeometry(2 + rng() * 4, 1 + rng() * 3, 2 + rng() * 4);
const rockMat = new THREE.MeshPhongMaterial({
color: WALL_COLORS[Math.floor(rng() * WALL_COLORS.length)], flatShading: true
});
const rock = new THREE.Mesh(rockGeo, rockMat);
rock.position.set((rng() - 0.5) * width * 0.6, -3 + rng(), segZ);
meshes.push(rock);
}
}
}
function generateCanyonWalls(meshes, obstacles, zStart, zEnd, width, height, rng) {
const segmentCount = Math.floor((zStart - zEnd) / 12);
for (let i = 0; i < segmentCount; i++) {
const t = i / segmentCount;
const segZ = zStart - t * (zStart - zEnd);
// Varying wall positions for organic feel
const leftOffset = Math.sin(zStart * 0.05 + i) * 3;
const rightOffset = Math.cos(zStart * 0.07 + i) * 3;
// Left wall - multiple stacked segments for jagged look
const leftX = -(width / 2 + rng() * 6 + leftOffset);
const leftH = height * (0.4 + rng() * 0.6);
const leftW = 2 + rng() * 5;
addWallSegment(meshes, obstacles, leftX, leftH, leftW, segZ, 'wall', rng);
// Right wall
const rightX = width / 2 + rng() * 6 + rightOffset;
const rightH = height * (0.4 + rng() * 0.6);
const rightW = 2 + rng() * 5;
addWallSegment(meshes, obstacles, rightX, rightH, rightW, segZ, 'wall', rng);
// Occasional wall overhangs
if (rng() > 0.7) {
const overhangSide = rng() > 0.5 ? -1 : 1;
const ohX = overhangSide * (width / 2 + rng() * 3);
addWallSegment(meshes, obstacles, ohX, height * 0.3, 4 + rng() * 3, segZ, 'wall', rng);
}
}
// Ceiling segments (sparse)
for (let i = 0; i < segmentCount / 2; i++) {
const segZ = zStart - (i / (segmentCount / 2)) * (zStart - zEnd);
if (rng() > 0.5)
continue;
const ceilW = width * (0.3 + rng() * 0.4);
addCeilingSegment(meshes, obstacles, ceilW, height * 1.1, segZ);
}
}
function addWallSegment(meshes, obstacles, x, h, w, z, type, rng) {
const geo = new THREE.BoxGeometry(w, h, 14);
// Pick color from palette with some variation
let colorIdx;
if (rng) {
colorIdx = Math.floor(rng() * WALL_COLORS.length);
}
else {
colorIdx = Math.floor(Math.random() * WALL_COLORS.length);
}
const baseColor = WALL_COLORS[colorIdx];
// Add slight emissive glow for atmosphere
const mat = new THREE.MeshPhongMaterial({
color: baseColor, flatShading: true,
emissive: 0x110822, emissiveIntensity: 0.2
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, h / 2 - 5, z);
meshes.push(mesh);
obstacles.push({
position: new THREE.Vector3(x, h / 2 - 5, z),
radius: Math.max(w, h) * 0.4,
mesh,
type: 'wall',
});
}
function addCeilingSegment(meshes, obstacles, w, y, z) {
const geo = new THREE.BoxGeometry(w, 4, 10);
const mat = new THREE.MeshPhongMaterial({ color: 0x663355, flatShading: true });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set((Math.random() - 0.5) * w * 0.3, y, z);
meshes.push(mesh);
obstacles.push({
position: new THREE.Vector3(mesh.position.x, mesh.position.y, mesh.position.z),
radius: Math.max(w, 4) * 0.35,
mesh,
type: 'wall',
});
}
function generateMonolith(meshes, obstacles, zStart, zEnd, width, height, rng) {
const x = (rng() - 0.5) * width * 0.7;
const y = 3 + rng() * (height * 0.6);
const z = zStart - rng() * (zStart - zEnd);
const h = 4 + rng() * 8;
const w = 1.5 + rng() * 2;
let geo;
if (rng() > 0.5) {
geo = new THREE.BoxGeometry(w, h, w);
}
else {
geo = new THREE.OctahedronGeometry(Math.max(w, h * 0.3), 0);
}
const mat = new THREE.MeshPhongMaterial({
color: 0x8844aa, flatShading: true, emissive: 0x221133, emissiveIntensity: 0.5
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, y, z);
meshes.push(mesh);
obstacles.push({
position: new THREE.Vector3(x, y, z),
radius: Math.max(w, h) * 0.45,
mesh,
type: 'monolith',
});
}
function generateSqueeze(meshes, obstacles, zStart, zEnd, width, height, rng) {
const squeezeZ = zStart - (zStart - zEnd) * (0.3 + rng() * 0.4);
const gapWidth = 6 + rng() * 4;
const centerX = (rng() - 0.5) * width * 0.2;
addWallSegment(meshes, obstacles, centerX - gapWidth / 2 - 1, height * 0.8, 3, squeezeZ, 'squeeze', rng);
addWallSegment(meshes, obstacles, centerX + gapWidth / 2 + 1, height * 0.8, 3, squeezeZ, 'squeeze', rng);
// Overhead bar for tight squeezes
if (rng() > 0.5) {
const geo = new THREE.BoxGeometry(gapWidth + 4, 2, 6);
const mat = new THREE.MeshPhongMaterial({ color: 0x773355, flatShading: true });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(centerX, height * 0.7, squeezeZ);
meshes.push(mesh);
obstacles.push({
position: new THREE.Vector3(centerX, height * 0.7, squeezeZ),
radius: (gapWidth + 4) * 0.5,
mesh,
type: 'squeeze',
});
}
}
function generateOrb(orbs, zStart, zEnd, width, height, rng) {
const x = (rng() - 0.5) * width * 0.6;
const y = 3 + rng() * (height * 0.5);
const z = zStart - rng() * (zStart - zEnd);
// Core orb - bright emissive for glow effect
const geo = new THREE.IcosahedronGeometry(0.7, 1);
const mat = new THREE.MeshBasicMaterial({
color: 0xffcc44, transparent: true, opacity: 0.95
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, y, z);
// Outer glow shell - larger and more transparent
const glowGeo = new THREE.IcosahedronGeometry(1.3, 1);
const glowMat = new THREE.MeshBasicMaterial({
color: 0xff6622, transparent: true, opacity: 0.25
});
const glowMesh = new THREE.Mesh(glowGeo, glowMat);
mesh.add(glowMesh);
// Inner bright core
const coreGeo = new THREE.IcosahedronGeometry(0.3, 0);
const coreMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
const coreMesh = new THREE.Mesh(coreGeo, coreMat);
mesh.add(coreMesh);
orbs.push({ position: new THREE.Vector3(x, y, z), collected: false, mesh });
}
function cleanupChunk(chunk) {
// Remove meshes from scene handled by main loop
chunk.meshes.length = 0;
chunk.obstacles.length = 0;
chunk.orbs.length = 0;
}
function getObstaclesInRange(playerZ, range) {
const result = [];
for (const [, chunk] of chunks) {
if (chunk.zEnd > playerZ + range || chunk.zStart < playerZ - range)
continue;
result.push(...chunk.obstacles);
}
return result;
}
function getOrbsInRange(playerZ, range) {
const result = [];
for (const [, chunk] of chunks) {
if (chunk.zEnd > playerZ + range || chunk.zStart < playerZ - range)
continue;
result.push(...chunk.orbs);
}
return result;
}
function getAllMeshes() {
const all = [];
for (const [, chunk] of chunks) {
all.push(...chunk.meshes);
}
for (const [, chunk] of chunks) {
for (const orb of chunk.orbs) {
if (!orb.collected && orb.mesh) {
all.push(orb.mesh);
}
}
}
return all;
}
function getAllObstacles() {
const all = [];
for (const [, chunk] of chunks) {
all.push(...chunk.obstacles);
}
return all;
}
function getAllOrbs() {
const all = [];
for (const [, chunk] of chunks) {
all.push(...chunk.orbs);
}
return all;
}
};
// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initParticles = initParticles;
exports.emitTrail = emitTrail;
exports.emitDebris = emitDebris;
exports.emitSparks = emitSparks;
exports.emitSpeedLines = emitSpeedLines;
exports.updateParticles = updateParticles;
exports.renderParticles = renderParticles;
exports.resetParticles = resetParticles;
let trailParticles = [];
let debrisParticles = [];
let sparkParticles = [];
let speedLineParticles = [];
const MAX_TRAIL_PARTICLES = 150;
const MAX_DEBRIS_PARTICLES = 120;
const MAX_SPARKS = 80;
const MAX_SPEED_LINES = 60;
// Particle meshes (reused)
let trailMeshes = [];
let debrisMeshes = [];
let sparkMeshes = [];
let speedLineMeshes = [];
function initParticles(scene) {
// Trail particles (engine exhaust)
const trailGeo = new THREE.BoxGeometry(0.12, 0.12, 0.12);
for (let i = 0; i < MAX_TRAIL_PARTICLES; i++) {
const mat = new THREE.MeshBasicMaterial({ color: 0xff4422, transparent: true });
const mesh = new THREE.Mesh(trailGeo.clone(), mat);
mesh.visible = false;
scene.add(mesh);
trailMeshes.push(mesh);
}
// Debris particles (small rocks flying past)
const debrisGeo = new THREE.BoxGeometry(0.1, 0.1, 0.3);
for (let i = 0; i < MAX_DEBRIS_PARTICLES; i++) {
const mat = new THREE.MeshBasicMaterial({ color: 0x886644, transparent: true });
const mesh = new THREE.Mesh(debrisGeo.clone(), mat);
mesh.visible = false;
scene.add(mesh);
debrisMeshes.push(mesh);
}
// Spark particles (near-miss sparks)
const sparkGeo = new THREE.BoxGeometry(0.2, 0.2, 0.2);
for (let i = 0; i < MAX_SPARKS; i++) {
const mat = new THREE.MeshBasicMaterial({ color: 0xffaa44, transparent: true });
const mesh = new THREE.Mesh(sparkGeo.clone(), mat);
mesh.visible = false;
scene.add(mesh);
sparkMeshes.push(mesh);
}
// Speed lines (streaks emphasizing velocity)
const speedLineGeo = new THREE.BoxGeometry(0.05, 0.05, 3);
for (let i = 0; i < MAX_SPEED_LINES; i++) {
const mat = new THREE.MeshBasicMaterial({ color: 0xffccaa, transparent: true });
const mesh = new THREE.Mesh(speedLineGeo.clone(), mat);
mesh.visible = false;
scene.add(mesh);
speedLineMeshes.push(mesh);
}
}
function emitTrail(position, speed) {
if (trailParticles.length >= MAX_TRAIL_PARTICLES)
return;
trailParticles.push({
position: new THREE.Vector3(position.x + (Math.random() - 0.5) * 0.4, position.y + (Math.random() - 0.5) * 0.4, position.z + 1.8),
velocity: new THREE.Vector3((Math.random() - 0.5) * 0.03, (Math.random() - 0.5) * 0.03, speed * 0.4),
life: 1.0,
maxLife: 0.5 + Math.random() * 0.4,
size: 0.12 + Math.random() * 0.12,
});
}
function emitDebris(playerPos, speed) {
if (debrisParticles.length >= MAX_DEBRIS_PARTICLES)
return;
debrisParticles.push({
position: new THREE.Vector3(playerPos.x + (Math.random() - 0.5) * 25, playerPos.y + (Math.random() - 0.5) * 18, playerPos.z - speed * 40 - Math.random() * 25),
velocity: new THREE.Vector3((Math.random() - 0.5) * 0.15, (Math.random() - 0.5) * 0.15, speed * 2.5 + Math.random() * speed),
life: 1.0,
maxLife: 0.3 + Math.random() * 0.4,
size: 0.06 + Math.random() * 0.12,
color: Math.random() > 0.5 ? 0x775544 : 0x998877,
});
}
function emitSparks(position) {
if (sparkParticles.length >= MAX_SPARKS)
return;
for (let i = 0; i < 6; i++) {
sparkParticles.push({
position: new THREE.Vector3(position.x + (Math.random() - 0.5) * 2, position.y + (Math.random() - 0.5) * 2, position.z + (Math.random() - 0.5) * 2),
velocity: new THREE.Vector3((Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4, (Math.random() - 0.5) * 0.4),
life: 1.0,
maxLife: 0.2 + Math.random() * 0.3,
size: 0.08 + Math.random() * 0.15,
color: 0xffaa44,
});
}
}
function emitSpeedLines(playerPos, speed) {
if (speedLineParticles.length >= MAX_SPEED_LINES)
return;
// Speed lines appear at the edges of vision, flying past quickly
const angle = Math.random() * Math.PI * 2;
const radius = 8 + Math.random() * 15;
speedLineParticles.push({
position: new THREE.Vector3(playerPos.x + Math.cos(angle) * radius, playerPos.y + (Math.random() - 0.5) * 20, playerPos.z - speed * 30 - Math.random() * 15),
velocity: new THREE.Vector3(Math.cos(angle) * 0.1, (Math.random() - 0.5) * 0.1, speed * 4 + Math.random() * speed * 2),
life: 1.0,
maxLife: 0.15 + Math.random() * 0.2,
size: 0.03 + Math.random() * 0.05,
color: 0xffccaa,
});
}
function updateParticles(dt) {
// Update all particle arrays
const updateArray = (arr) => {
for (let i = arr.length - 1; i >= 0; i--) {
const p = arr[i];
p.life -= dt / p.maxLife;
if (p.life <= 0) {
arr.splice(i, 1);
continue;
}
p.position.x += p.velocity.x * dt * 60;
p.position.y += p.velocity.y * dt * 60;
p.position.z += p.velocity.z * dt * 60;
}
};
updateArray(trailParticles);
updateArray(debrisParticles);
updateArray(sparkParticles);
updateArray(speedLineParticles);
}
function renderParticles() {
// Render trail particles
for (let i = 0; i < MAX_TRAIL_PARTICLES; i++) {
const mesh = trailMeshes[i];
if (i < trailParticles.length) {
const p = trailParticles[i];
mesh.visible = true;
mesh.position.copy(p.position);
const s = p.size * p.life;
mesh.scale.set(s, s, s);
mesh.material.opacity = p.life * 0.5;
}
else {
mesh.visible = false;
}
}
// Render debris particles
for (let i = 0; i < MAX_DEBRIS_PARTICLES; i++) {
const mesh = debrisMeshes[i];
if (i < debrisParticles.length) {
const p = debrisParticles[i];
mesh.visible = true;
mesh.position.copy(p.position);
const s = p.size * p.life;
mesh.scale.set(s, s, s * 2);
mesh.material.opacity = p.life * 0.35;
}
else {
mesh.visible = false;
}
}
// Render spark particles
for (let i = 0; i < MAX_SPARKS; i++) {
const mesh = sparkMeshes[i];
if (i < sparkParticles.length) {
const p = sparkParticles[i];
mesh.visible = true;
mesh.position.copy(p.position);
const s = p.size * p.life;
mesh.scale.set(s, s, s);
mesh.material.opacity = p.life * 0.7;
}
else {
mesh.visible = false;
}
}
// Render speed lines
for (let i = 0; i < MAX_SPEED_LINES; i++) {
const mesh = speedLineMeshes[i];
if (i < speedLineParticles.length) {
const p = speedLineParticles[i];
mesh.visible = true;
mesh.position.copy(p.position);
// Orient the line along its velocity direction
const velLen = Math.sqrt(p.velocity.x * p.velocity.x +
p.velocity.y * p.velocity.y +
p.velocity.z * p.velocity.z);
if (velLen > 0) {
mesh.lookAt(p.position.x + p.velocity.x, p.position.y + p.velocity.y, p.position.z + p.velocity.z);
}
const s = p.size * p.life;
mesh.scale.set(s, s, 1);
mesh.material.opacity = p.life * 0.3;
}
else {
mesh.visible = false;
}
}
}
function resetParticles() {
trailParticles.length = 0;
debrisParticles.length = 0;
sparkParticles.length = 0;
speedLineParticles.length = 0;
}
};
// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initHUD = initHUD;
exports.showHUD = showHUD;
exports.hideHUD = hideHUD;
exports.showStartScreen = showStartScreen;
exports.showGameOver = showGameOver;
exports.updateHUD = updateHUD;
const types_1 = require("./types");
let scoreEl;
let multiplierEl;
let speedBarEl;
let energyBarEl;
let turboBarEl;
let hudContainer;
let startScreen;
let gameOverScreen;
function initHUD() {
scoreEl = document.getElementById('score-display');
multiplierEl = document.getElementById('multiplier-display');
speedBarEl = document.getElementById('speed-bar');
energyBarEl = document.getElementById('energy-bar');
turboBarEl = document.getElementById('turbo-bar');
hudContainer = document.getElementById('hud');
startScreen = document.getElementById('start-screen');
gameOverScreen = document.getElementById('game-over-screen');
// Start button handler
const startBtn = document.getElementById('start-btn');
if (startBtn) {
startBtn.addEventListener('click', () => {
if (typeof window !== 'undefined' && window.onStartGame) {
window.onStartGame();
}
});
}
// Restart button handler
const restartBtn = document.getElementById('restart-btn');
if (restartBtn) {
restartBtn.addEventListener('click', () => {
if (typeof window !== 'undefined' && window.onRestartGame) {
window.onRestartGame();
}
});
}
}
function showHUD() {
if (hudContainer)
hudContainer.classList.remove('hidden');
if (startScreen)
startScreen.classList.add('hidden');
if (gameOverScreen)
gameOverScreen.classList.add('hidden');
}
function hideHUD() {
if (hudContainer)
hudContainer.classList.add('hidden');
}
function showStartScreen() {
if (startScreen)
startScreen.classList.remove('hidden');
if (gameOverScreen)
gameOverScreen.classList.add('hidden');
if (hudContainer)
hudContainer.classList.add('hidden');
}
function showGameOver(state) {
if (gameOverScreen)
gameOverScreen.classList.remove('hidden');
if (hudContainer)
hudContainer.classList.add('hidden');
const finalScore = document.getElementById('final-score');
const maxSpeedEl = document.getElementById('max-speed');
const distanceEl = document.getElementById('distance');
if (finalScore)
finalScore.textContent = Math.floor(state.score).toLocaleString();
if (maxSpeedEl)
maxSpeedEl.textContent = Math.floor(state.maxSpeed * 100) + ' km/h';
if (distanceEl)
distanceEl.textContent = Math.floor(state.distance / 100) + ' km';
}
function updateHUD(player, state) {
// Score
if (scoreEl)
scoreEl.textContent = Math.floor(state.score).toLocaleString();
// Multiplier - pulse effect on near miss
if (multiplierEl) {
multiplierEl.textContent = 'x' + state.multiplier.toFixed(1);
if (state.multiplier > 2.0) {
multiplierEl.classList.add('near-miss-pulse');
setTimeout(() => multiplierEl?.classList.remove('near-miss-pulse'), 200);
}
}
// Speed bar
if (speedBarEl) {
const speedRatio = Math.min(1, player.speed / types_1.CONFIG.BOOST_SPEED);
speedBarEl.style.height = (speedRatio * 100) + '%';
}
// Energy bar
if (energyBarEl) {
const energyRatio = Math.max(0, player.energy / types_1.CONFIG.ENERGY_MAX);
energyBarEl.style.width = (energyRatio * 100) + '%';
// Color change when low
if (energyRatio < 0.25) {
energyBarEl.style.background = 'linear-gradient(90deg, #ff0044, #ff2266)';
energyBarEl.style.animation = 'energyWarning 0.5s infinite';
}
else {
energyBarEl.style.background = 'linear-gradient(90deg, #ff2266, #ffaa44)';
energyBarEl.style.animation = '';
}
}
// Turbo bar
if (turboBarEl) {
const turboRatio = Math.max(0, player.turbo / types_1.CONFIG.TURBO_MAX);
turboBarEl.style.width = (turboRatio * 100) + '%';
// Glow when full
if (turboRatio >= 1.0) {
turboBarEl.style.animation = 'turboReady 0.8s infinite';
}
else {
turboBarEl.style.animation = '';
}
}
}
};
// ── module: src/postprocessing.ts ──
__mods["src/postprocessing.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initPostProcessing = initPostProcessing;
exports.renderWithEffects = renderWithEffects;
exports.updateBloom = updateBloom;
exports.triggerCameraShake = triggerCameraShake;
exports.getCameraShake = getCameraShake;
exports.resetCameraShake = resetCameraShake;
exports.getComposer = getComposer;
exports.resizePostProcessing = resizePostProcessing;
const types_1 = require("./types");
let cameraShakeAmount = 0;
const cameraShakeDecay = 0.9;
// Simple glow sprites for bloom-like effect on orbs and engine
let glowSprites = [];
function initPostProcessing(_renderer, _scene, _camera) {
// Post-processing via CDN doesn't chain properly.
// We'll use emissive materials for glow instead.
}
function renderWithEffects() {
// No-op - rendering handled in main loop
}
function updateBloom(speed, _boosting) {
// Adjust glow sprite intensity based on speed
const intensity = 0.5 + (speed / types_1.CONFIG.BOOST_SPEED) * 0.5;
for (const sprite of glowSprites) {
if (sprite.material.opacity !== undefined) {
sprite.material.opacity = Math.min(1, intensity);
}
}
}
function triggerCameraShake(amount) {
cameraShakeAmount = Math.min(cameraShakeAmount + amount, 1.0);
}
function getCameraShake() {
if (cameraShakeAmount < 0.01)
return new THREE.Vector3(0, 0, 0);
const shake = new THREE.Vector3((Math.random() - 0.5) * cameraShakeAmount * 2, (Math.random() - 0.5) * cameraShakeAmount * 1.5, (Math.random() - 0.5) * cameraShakeAmount * 0.5);
cameraShakeAmount *= cameraShakeDecay;
if (cameraShakeAmount < 0.01)
cameraShakeAmount = 0;
return shake;
}
function resetCameraShake() {
cameraShakeAmount = 0;
}
function getComposer() {
return null; // No composer - we render directly
}
function resizePostProcessing(_width, _height) {
// No-op
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"typescript","sessionId":"a1f1db67-b77d-4748-9e3a-67aee02eb577","tokensIn":2909774,"tokensOut":46150,"tokensTotal":2955924,"turns":78,"toolCalls":77,"failedToolCalls":4,"timestamp":"2026-07-30T16:38:34.942Z"} -->