Canyon Run
seed 2.0 code · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 54,093 bytes · SHA-256 e5ba2e100106
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>High-Speed Procedural Canyon Racer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #0a0a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#game-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
#hud {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
}
.hud-element {
position: absolute;
pointer-events: none;
}
#score-container {
top: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
#score {
font-size: 48px;
font-weight: 100;
color: #fff;
text-shadow: 0 0 20px rgba(255, 150, 50, 0.8);
letter-spacing: 4px;
}
#multiplier {
font-size: 24px;
font-weight: 300;
color: #ff9632;
text-shadow: 0 0 15px rgba(255, 150, 50, 0.6);
margin-top: 5px;
transition: transform 0.1s, color 0.1s;
}
#multiplier.pulse {
transform: scale(1.5);
color: #fff;
}
#speed-container {
bottom: 80px;
left: 50%;
transform: translateX(-50%);
width: 300px;
text-align: center;
}
#speed-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
#speed-fill {
height: 100%;
background: linear-gradient(90deg, #ff9632, #ff5050);
width: 0%;
transition: width 0.1s;
box-shadow: 0 0 10px rgba(255, 150, 50, 0.8);
}
#speed-label {
font-size: 14px;
color: rgba(255, 255, 255, 0.6);
margin-top: 8px;
letter-spacing: 2px;
}
#energy-container {
bottom: 30px;
left: 50%;
transform: translateX(-50%);
width: 400px;
text-align: center;
}
#energy-bar {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
overflow: hidden;
}
#energy-fill {
height: 100%;
background: linear-gradient(90deg, #00ffaa, #00aaff);
width: 100%;
transition: width 0.1s;
box-shadow: 0 0 15px rgba(0, 255, 170, 0.8);
}
#turbo-container {
right: 30px;
bottom: 50%;
transform: translateY(50%);
width: 8px;
height: 200px;
}
#turbo-bar {
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
overflow: hidden;
}
#turbo-fill {
position: absolute;
bottom: 0;
width: 100%;
height: 0%;
background: linear-gradient(180deg, #ff3366, #ff9632);
transition: height 0.1s;
box-shadow: 0 0 15px rgba(255, 51, 102, 0.8);
}
#turbo-label {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(50%) rotate(-90deg);
transform-origin: right center;
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
white-space: nowrap;
}
#controls-hint {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
letter-spacing: 2px;
text-align: center;
}
#start-screen, #game-over-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(10, 10, 26, 0.95);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 200;
pointer-events: auto;
}
#game-over-screen {
display: none;
}
.title {
font-size: 64px;
font-weight: 100;
color: #fff;
text-shadow: 0 0 40px rgba(255, 150, 50, 0.8);
letter-spacing: 8px;
margin-bottom: 20px;
}
.subtitle {
font-size: 18px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 4px;
margin-bottom: 60px;
}
.start-btn {
padding: 20px 60px;
font-size: 18px;
font-weight: 300;
letter-spacing: 4px;
color: #fff;
background: linear-gradient(135deg, rgba(255, 150, 50, 0.3), rgba(255, 51, 102, 0.3));
border: 1px solid rgba(255, 150, 50, 0.5);
cursor: pointer;
transition: all 0.3s;
pointer-events: auto;
}
.start-btn:hover {
background: linear-gradient(135deg, rgba(255, 150, 50, 0.5), rgba(255, 51, 102, 0.5));
box-shadow: 0 0 30px rgba(255, 150, 50, 0.5);
transform: scale(1.05);
}
.stats {
margin: 40px 0;
text-align: center;
}
.stat-row {
display: flex;
justify-content: space-between;
width: 400px;
padding: 15px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.stat-label {
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
font-size: 14px;
}
.stat-value {
color: #ff9632;
font-size: 24px;
font-weight: 200;
text-shadow: 0 0 15px rgba(255, 150, 50, 0.5);
}
.instructions {
margin-top: 40px;
text-align: center;
color: rgba(255, 255, 255, 0.4);
font-size: 14px;
line-height: 2;
letter-spacing: 1px;
}
</style>
</head>
<body>
<div id="game-container"></div>
<div id="hud">
<div id="score-container" class="hud-element">
<div id="score">0</div>
<div id="multiplier">x1.0</div>
</div>
<div id="speed-container" class="hud-element">
<div id="speed-bar">
<div id="speed-fill"></div>
</div>
<div id="speed-label">SPEED</div>
</div>
<div id="energy-container" class="hud-element">
<div id="energy-bar">
<div id="energy-fill"></div>
</div>
</div>
<div id="turbo-container" class="hud-element">
<div id="turbo-label">TURBO</div>
<div id="turbo-bar">
<div id="turbo-fill"></div>
</div>
</div>
<div id="controls-hint">
WASD / ARROWS TO STEER • SPACE TO BOOST
</div>
</div>
<div id="start-screen">
<div class="title">CANYON RACER</div>
<div class="subtitle">FLY FAST. DARE CLOSE. STAY ALIVE.</div>
<button class="start-btn" id="start-btn">START RACE</button>
<div class="instructions">
<div>W/↑ - PITCH DOWN (FASTER)</div>
<div>S/↓ - PITCH UP (SLOWER)</div>
<div>A/← D/→ - BANK LEFT/RIGHT</div>
<div>SPACE - TURBO BOOST</div>
<div style="margin-top: 20px; color: #00ffaa;">COLLECT ORBS TO RECHARGE ENERGY</div>
<div style="color: #ff9632;">FLY CLOSE TO WALLS FOR MULTIPLIER</div>
</div>
</div>
<div id="game-over-screen">
<div class="title">GAME OVER</div>
<div class="stats">
<div class="stat-row">
<span class="stat-label">FINAL SCORE</span>
<span class="stat-value" id="final-score">0</span>
</div>
<div class="stat-row">
<span class="stat-label">MAX SPEED</span>
<span class="stat-value" id="final-speed">0</span>
</div>
<div class="stat-row">
<span class="stat-label">MAX MULTIPLIER</span>
<span class="stat-value" id="final-multiplier">x1.0</span>
</div>
</div>
<button class="start-btn" id="restart-btn">RACE AGAIN</button>
</div>
<script>
// ============== GAME STATE ==============
const GameState = {
MENU: 'menu',
PLAYING: 'playing',
GAMEOVER: 'gameover'
};
let currentState = GameState.MENU;
let score = 0;
let maxSpeed = 0;
let maxMultiplier = 1.0;
// ============== INPUT HANDLER ==============
const Input = {
pitchUp: false,
pitchDown: false,
bankLeft: false,
bankRight: false,
boost: false
};
document.addEventListener('keydown', (e) => {
switch(e.code) {
case 'KeyW': case 'ArrowUp': Input.pitchDown = true; break;
case 'KeyS': case 'ArrowDown': Input.pitchUp = true; break;
case 'KeyA': case 'ArrowLeft': Input.bankLeft = true; break;
case 'KeyD': case 'ArrowRight': Input.bankRight = true; break;
case 'Space': Input.boost = true; e.preventDefault(); break;
}
});
document.addEventListener('keyup', (e) => {
switch(e.code) {
case 'KeyW': case 'ArrowUp': Input.pitchDown = false; break;
case 'KeyS': case 'ArrowDown': Input.pitchUp = false; break;
case 'KeyA': case 'ArrowLeft': Input.bankLeft = false; break;
case 'KeyD': case 'ArrowRight': Input.bankRight = false; break;
case 'Space': Input.boost = false; break;
}
});
// ============== AUDIO SYSTEM ==============
class AudioSystem {
constructor() {
this.audioContext = null;
this.masterGain = null;
this.engineOsc = null;
this.engineGain = null;
}
init() {
const AudioContext = window.AudioContext || window.webkitAudioContext;
this.audioContext = new AudioContext();
this.masterGain = this.audioContext.createGain();
this.masterGain.gain.value = 0.3;
this.masterGain.connect(this.audioContext.destination);
// Engine hum
this.engineOsc = this.audioContext.createOscillator();
this.engineGain = this.audioContext.createGain();
this.engineOsc.type = 'sawtooth';
this.engineOsc.frequency.value = 60;
this.engineGain.gain.value = 0.1;
this.engineOsc.connect(this.engineGain);
this.engineGain.connect(this.masterGain);
this.engineOsc.start();
// LFO for engine pulse
this.lfo = this.audioContext.createOscillator();
this.lfoGain = this.audioContext.createGain();
this.lfo.frequency.value = 2;
this.lfoGain.gain.value = 10;
this.lfo.connect(this.lfoGain);
this.lfoGain.connect(this.engineOsc.frequency);
this.lfo.start();
}
update(speed, boosting) {
if (!this.engineOsc) return;
const baseFreq = 60 + speed * 0.5;
this.engineOsc.frequency.setTargetAtTime(baseFreq, this.audioContext.currentTime, 0.1);
this.engineGain.gain.setTargetAtTime(boosting ? 0.2 : 0.1, this.audioContext.currentTime, 0.1);
this.lfo.frequency.setTargetAtTime(boosting ? 8 : 2, this.audioContext.currentTime, 0.1);
}
playCollect() {
if (!this.audioContext) return;
const osc = this.audioContext.createOscillator();
const gain = this.audioContext.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, this.audioContext.currentTime);
osc.frequency.exponentialRampToValueAtTime(1760, this.audioContext.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, this.audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.audioContext.currentTime + 0.3);
}
playNearMiss(intensity) {
if (!this.audioContext) return;
const noise = this.audioContext.createBufferSource();
const bufferSize = this.audioContext.sampleRate * 0.2;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * (1 - i / bufferSize);
}
noise.buffer = buffer;
const filter = this.audioContext.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 1000 + intensity * 2000;
filter.Q.value = 1;
const gain = this.audioContext.createGain();
gain.gain.value = 0.15 * intensity;
noise.connect(filter);
filter.connect(gain);
gain.connect(this.masterGain);
noise.start();
}
playCrash() {
if (!this.audioContext) return;
const noise = this.audioContext.createBufferSource();
const bufferSize = this.audioContext.sampleRate * 0.5;
const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / bufferSize, 0.5);
}
noise.buffer = buffer;
const filter = this.audioContext.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 200;
const gain = this.audioContext.createGain();
gain.gain.value = 0.4;
noise.connect(filter);
filter.connect(gain);
gain.connect(this.masterGain);
noise.start();
}
}
const audio = new AudioSystem();
// ============== THREE.JS SETUP ==============
const container = document.getElementById('game-container');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a1a);
scene.fog = new THREE.FogExp2(0x1a0a2e, 0.003);
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 3, -8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.appendChild(renderer.domElement);
// Post-processing disabled for file:// loading - game uses emissive materials for glow effect
// Lighting
const ambientLight = new THREE.AmbientLight(0x4a2a6e, 0.4);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xff9966, 1.2);
sunLight.position.set(50, 100, -50);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.camera.near = 0.5;
sunLight.shadow.camera.far = 500;
sunLight.shadow.camera.left = -100;
sunLight.shadow.camera.right = 100;
sunLight.shadow.camera.top = 100;
sunLight.shadow.camera.bottom = -100;
scene.add(sunLight);
const fillLight = new THREE.DirectionalLight(0x6644aa, 0.5);
fillLight.position.set(-30, 20, 30);
scene.add(fillLight);
// ============== PLAYER ==============
class Player {
constructor() {
this.group = new THREE.Group();
// Main body - sleek arrow shape
const bodyGeo = new THREE.ConeGeometry(0.4, 2.5, 4);
bodyGeo.rotateX(Math.PI / 2);
const bodyMat = new THREE.MeshStandardMaterial({
color: 0x4488ff,
metalness: 0.8,
roughness: 0.2,
flatShading: true
});
this.body = new THREE.Mesh(bodyGeo, bodyMat);
this.body.castShadow = true;
this.group.add(this.body);
// Wings
const wingGeo = new THREE.BoxGeometry(2.5, 0.05, 0.8);
const wingMat = new THREE.MeshStandardMaterial({
color: 0x3366cc,
metalness: 0.9,
roughness: 0.1,
flatShading: true
});
this.wings = new THREE.Mesh(wingGeo, wingMat);
this.wings.position.z = 0.3;
this.wings.castShadow = true;
this.group.add(this.wings);
// Engine glow
const engineGeo = new THREE.SphereGeometry(0.2, 8, 8);
const engineMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa });
this.engineGlow = new THREE.Mesh(engineGeo, engineMat);
this.engineGlow.position.z = 1.4;
this.group.add(this.engineGlow);
// Engine point light
this.engineLight = new THREE.PointLight(0x00ffaa, 2, 10);
this.engineLight.position.z = 2;
this.group.add(this.engineLight);
// Physics properties
this.position = new THREE.Vector3(0, 2, 0);
this.velocity = new THREE.Vector3(0, 0, 0);
this.targetBank = 0;
this.currentBank = 0;
this.targetPitch = 0;
this.currentPitch = 0;
this.baseSpeed = 0.5;
this.speed = this.baseSpeed;
this.maxSpeed = 3.0;
this.energy = 100;
this.turbo = 0;
this.isBoosting = false;
this.multiplier = 1.0;
this.multiplierDecay = 0.005;
// Collision
this.boundingRadius = 0.8;
scene.add(this.group);
}
reset() {
this.position.set(0, 2, 0);
this.velocity.set(0, 0, 0);
this.targetBank = 0;
this.currentBank = 0;
this.targetPitch = 0;
this.currentPitch = 0;
this.speed = this.baseSpeed;
this.energy = 100;
this.turbo = 0;
this.isBoosting = false;
this.multiplier = 1.0;
}
update(deltaTime, obstacles, orbs) {
// Input
this.targetBank = 0;
this.targetPitch = 0;
if (Input.bankLeft) this.targetBank = 1.0;
if (Input.bankRight) this.targetBank = -1.0;
if (Input.pitchDown) this.targetPitch = -0.5;
if (Input.pitchUp) this.targetPitch = 0.5;
// Smooth interpolation
this.currentBank += (this.targetBank - this.currentBank) * 0.1;
this.currentPitch += (this.targetPitch - this.currentPitch) * 0.1;
// Speed based on pitch (pitch down = faster)
const targetSpeed = this.baseSpeed - this.currentPitch * 0.8;
// Boost logic
if (Input.boost && this.turbo > 0.1) {
this.isBoosting = true;
this.turbo -= deltaTime * 30;
if (this.turbo < 0) this.turbo = 0;
} else {
this.isBoosting = false;
}
const boostMultiplier = this.isBoosting ? 2.0 : 1.0;
this.speed += (targetSpeed * boostMultiplier - this.speed) * 0.05;
// Clamp speed
const actualMaxSpeed = this.maxSpeed * boostMultiplier;
if (this.speed > actualMaxSpeed) this.speed = actualMaxSpeed;
if (this.speed < 0.1) this.speed = 0.1;
// Energy decay
this.energy -= deltaTime * (3 + this.speed * 2);
if (this.energy < 0) {
this.energy = 0;
return false; // Game over
}
// Build turbo when at high speed
if (this.speed > this.baseSpeed * 1.5 && !this.isBoosting) {
this.turbo += deltaTime * 15;
if (this.turbo > 100) this.turbo = 100;
}
// Movement
const moveSpeed = this.speed * 60 * deltaTime;
this.velocity.z = moveSpeed;
// Horizontal movement based on bank
this.velocity.x = this.currentBank * moveSpeed * 0.8;
// Vertical movement based on pitch
this.velocity.y = -this.currentPitch * moveSpeed * 0.5;
this.position.add(this.velocity);
// Clamp vertical position
if (this.position.y < -20) this.position.y = -20;
if (this.position.y > 80) this.position.y = 80;
// Multiplier decay
this.multiplier -= this.multiplierDecay;
if (this.multiplier < 1.0) this.multiplier = 1.0;
// Update visual transform
this.group.position.copy(this.position);
this.group.rotation.z = this.currentBank * 0.8;
this.group.rotation.x = this.currentPitch * 0.3;
// Engine glow intensity
const glowIntensity = 0.8 + this.speed * 0.3 + (this.isBoosting ? 1.5 : 0);
this.engineLight.intensity = glowIntensity;
this.engineGlow.scale.setScalar(1 + this.speed * 0.2 + (this.isBoosting ? 0.5 : 0));
// Add turbo to score
if (this.isBoosting) {
this.multiplier += 0.01;
}
return true;
}
}
// ============== PARTICLE SYSTEMS ==============
class ParticleSystem {
constructor() {
// Trail particles behind player
this.trailParticles = [];
this.trailGeo = new THREE.SphereGeometry(0.1, 4, 4);
this.trailMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa, transparent: true });
// Debris/dust particles flying past
this.debrisCount = 200;
const debrisGeo = new THREE.BufferGeometry();
const positions = new Float32Array(this.debrisCount * 3);
const colors = new Float32Array(this.debrisCount * 3);
for (let i = 0; i < this.debrisCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 200;
positions[i * 3 + 1] = (Math.random() - 0.5) * 100;
positions[i * 3 + 2] = Math.random() * 500 - 100;
const color = new THREE.Color();
color.setHSL(0.08 + Math.random() * 0.1, 0.5, 0.3 + Math.random() * 0.3);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
}
debrisGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
debrisGeo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const debrisMat = new THREE.PointsMaterial({
size: 0.3,
vertexColors: true,
transparent: true,
opacity: 0.6
});
this.debris = new THREE.Points(debrisGeo, debrisMat);
scene.add(this.debris);
}
emitTrail(player) {
if (this.trailParticles.length > 30) {
const p = this.trailParticles.shift();
scene.remove(p.mesh);
}
const mesh = new THREE.Mesh(this.trailGeo, this.trailMat.clone());
mesh.position.copy(player.position);
mesh.position.z += 1.5;
mesh.position.x += (Math.random() - 0.5) * 0.5;
mesh.position.y += (Math.random() - 0.5) * 0.3;
scene.add(mesh);
this.trailParticles.push({
mesh: mesh,
life: 1.0,
speed: player.speed
});
}
update(deltaTime, playerSpeed, playerPos) {
// Update trail particles
for (let i = this.trailParticles.length - 1; i >= 0; i--) {
const p = this.trailParticles[i];
p.life -= deltaTime * 2;
p.mesh.position.z += p.speed * deltaTime * 30;
p.mesh.scale.setScalar(p.life);
p.mesh.material.opacity = p.life;
if (p.life <= 0) {
scene.remove(p.mesh);
this.trailParticles.splice(i, 1);
}
}
// Update debris - move toward camera
const positions = this.debris.geometry.attributes.position.array;
for (let i = 0; i < this.debrisCount; i++) {
positions[i * 3 + 2] -= playerSpeed * deltaTime * 120;
// Recycle when behind camera
if (positions[i * 3 + 2] < -50) {
positions[i * 3] = playerPos.x + (Math.random() - 0.5) * 200;
positions[i * 3 + 1] = playerPos.y + (Math.random() - 0.5) * 100;
positions[i * 3 + 2] = 400 + Math.random() * 200;
}
}
this.debris.geometry.attributes.position.needsUpdate = true;
}
}
// ============== PROCEDURAL GENERATION ==============
class WorldGenerator {
constructor() {
this.chunkSize = 100;
this.chunks = [];
this.obstacles = [];
this.orbs = [];
this.lastChunkZ = 0;
this.difficulty = 1;
// Materials
this.wallMat = new THREE.MeshStandardMaterial({
color: 0x4a3a5e,
metalness: 0.3,
roughness: 0.8,
flatShading: true
});
this.wallHighlightMat = new THREE.MeshStandardMaterial({
color: 0x6a4a7e,
metalness: 0.4,
roughness: 0.7,
flatShading: true
});
this.monolithMat = new THREE.MeshStandardMaterial({
color: 0x2a2a4e,
metalness: 0.6,
roughness: 0.4,
flatShading: true
});
this.orbMat = new THREE.MeshBasicMaterial({ color: 0x00ffaa });
}
reset() {
for (const chunk of this.chunks) {
scene.remove(chunk.mesh);
}
for (const obs of this.obstacles) {
scene.remove(obs.mesh);
}
for (const orb of this.orbs) {
scene.remove(orb.mesh);
scene.remove(orb.light);
}
this.chunks = [];
this.obstacles = [];
this.orbs = [];
this.lastChunkZ = 0;
this.difficulty = 1;
}
generateChunk(startZ) {
const chunk = {
startZ: startZ,
endZ: startZ + this.chunkSize,
mesh: new THREE.Group(),
obstacles: [],
orbs: []
};
// Canyon walls - sine wave pattern
const segments = 10;
const segLength = this.chunkSize / segments;
for (let i = 0; i < segments; i++) {
const z = startZ + i * segLength;
const nextZ = z + segLength;
// Vary canyon width based on difficulty and position
const widthOffset = Math.sin(z * 0.01 + this.difficulty) * 15 * this.difficulty;
const baseWidth = 40 - this.difficulty * 5;
const canyonWidth = Math.max(baseWidth, 20);
// Left wall segment
const leftX = -canyonWidth / 2 + widthOffset;
this.createWallSegment(chunk.mesh, leftX, z, nextZ, true);
// Right wall segment
const rightX = canyonWidth / 2 + widthOffset;
this.createWallSegment(chunk.mesh, rightX, z, nextZ, false);
// Add monoliths (floating obstacles)
if (Math.random() < 0.3 * this.difficulty) {
const monoX = (Math.random() - 0.5) * (canyonWidth - 15);
const monoY = 5 + Math.random() * 20;
const monoZ = z + segLength / 2;
this.createMonolith(chunk.mesh, monoX, monoY, monoZ, chunk.obstacles);
}
// Add tight squeeze obstacles
if (Math.random() < 0.2 * this.difficulty && i > 0 && i < segments - 1) {
this.createTightSqueeze(chunk.mesh, z + segLength / 2, canyonWidth, widthOffset, chunk.obstacles);
}
// Add energy orbs
const orbCount = 1 + Math.floor(Math.random() * 3);
for (let o = 0; o < orbCount; o++) {
const orbX = (Math.random() - 0.5) * (canyonWidth - 10);
const orbY = 2 + Math.random() * 15;
const orbZ = z + Math.random() * segLength;
this.createOrb(chunk.mesh, orbX, orbY, orbZ, chunk.orbs);
}
}
scene.add(chunk.mesh);
this.chunks.push(chunk);
this.obstacles.push(...chunk.obstacles);
this.orbs.push(...chunk.orbs);
}
createWallSegment(parent, x, startZ, endZ, isLeft) {
const height = 30 + Math.random() * 20;
const depth = endZ - startZ;
const thickness = 10 + Math.random() * 10;
// Main wall body
const geo = new THREE.BoxGeometry(thickness, height, depth);
const wall = new THREE.Mesh(geo, isLeft ? this.wallMat : this.wallHighlightMat);
wall.position.set(
x + (isLeft ? -thickness / 2 : thickness / 2),
height / 2 - 10,
startZ + depth / 2
);
wall.castShadow = true;
wall.receiveShadow = true;
parent.add(wall);
// Add some rock formations
for (let i = 0; i < 3; i++) {
const rockGeo = new THREE.DodecahedronGeometry(2 + Math.random() * 4, 0);
const rock = new THREE.Mesh(rockGeo, this.wallMat);
rock.position.set(
x + (isLeft ? -1 : 1) * (2 + Math.random() * 5),
Math.random() * height - 5,
startZ + Math.random() * depth
);
rock.rotation.set(Math.random(), Math.random(), Math.random());
rock.castShadow = true;
parent.add(rock);
}
}
createMonolith(parent, x, y, z, obstacleList) {
const height = 15 + Math.random() * 25;
const width = 3 + Math.random() * 4;
const geo = new THREE.BoxGeometry(width, height, width);
const monolith = new THREE.Mesh(geo, this.monolithMat);
monolith.position.set(x, y, z);
monolith.castShadow = true;
monolith.receiveShadow = true;
parent.add(monolith);
// Add glow on top
const topGeo = new THREE.OctahedronGeometry(width * 0.6, 0);
const topMat = new THREE.MeshBasicMaterial({ color: 0xff5566 });
const top = new THREE.Mesh(topGeo, topMat);
top.position.set(x, y + height / 2 + 2, z);
parent.add(top);
const light = new THREE.PointLight(0xff5566, 1, 15);
light.position.copy(top.position);
parent.add(light);
obstacleList.push({
mesh: monolith,
position: new THREE.Vector3(x, y, z),
radius: width * 0.7,
type: 'monolith'
});
}
createTightSqueeze(parent, z, canyonWidth, offset, obstacleList) {
const gapSize = 8 - this.difficulty;
const blockerWidth = (canyonWidth - gapSize) / 2;
// Left blocker
const leftGeo = new THREE.BoxGeometry(blockerWidth, 20, 8);
const left = new THREE.Mesh(leftGeo, this.monolithMat);
left.position.set(-canyonWidth / 2 + offset + blockerWidth / 2, 5, z);
left.castShadow = true;
parent.add(left);
// Right blocker
const right = new THREE.Mesh(leftGeo, this.monolithMat);
right.position.set(canyonWidth / 2 + offset - blockerWidth / 2, 5, z);
right.castShadow = true;
parent.add(right);
obstacleList.push({
mesh: left,
position: new THREE.Vector3(left.position.x, left.position.y, z),
radius: blockerWidth * 0.6,
type: 'gap'
});
obstacleList.push({
mesh: right,
position: new THREE.Vector3(right.position.x, right.position.y, z),
radius: blockerWidth * 0.6,
type: 'gap'
});
}
createOrb(parent, x, y, z, orbList) {
const geo = new THREE.IcosahedronGeometry(0.8, 0);
const orb = new THREE.Mesh(geo, this.orbMat);
orb.position.set(x, y, z);
parent.add(orb);
const light = new THREE.PointLight(0x00ffaa, 2, 12);
light.position.copy(orb.position);
parent.add(light);
orbList.push({
mesh: orb,
light: light,
position: new THREE.Vector3(x, y, z),
radius: 1.2,
collected: false,
rotationOffset: Math.random() * Math.PI * 2
});
}
update(playerPos, score) {
// Update difficulty
this.difficulty = 1 + score / 5000;
if (this.difficulty > 4) this.difficulty = 4;
// Generate new chunks
while (this.lastChunkZ < playerPos.z + 500) {
this.generateChunk(this.lastChunkZ);
this.lastChunkZ += this.chunkSize;
}
// Remove old chunks
for (let i = this.chunks.length - 1; i >= 0; i--) {
const chunk = this.chunks[i];
if (chunk.endZ < playerPos.z - 100) {
scene.remove(chunk.mesh);
// Remove from obstacles list
this.obstacles = this.obstacles.filter(o => !chunk.obstacles.includes(o));
this.orbs = this.orbs.filter(o => !chunk.orbs.includes(o));
this.chunks.splice(i, 1);
}
}
// Animate orbs
const time = Date.now() * 0.001;
for (const orb of this.orbs) {
if (!orb.collected) {
orb.mesh.rotation.y = time * 2 + orb.rotationOffset;
orb.mesh.rotation.x = time * 1.5;
orb.mesh.position.y = orb.position.y + Math.sin(time * 3 + orb.rotationOffset) * 0.5;
}
}
}
checkCollisions(player) {
const playerPos = player.position;
let hitSomething = false;
let nearMiss = false;
let nearMissIntensity = 0;
// Check obstacles
for (const obs of this.obstacles) {
const dx = playerPos.x - obs.position.x;
const dy = playerPos.y - obs.position.y;
const dz = playerPos.z - obs.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
const minDist = player.boundingRadius + obs.radius;
if (dist < minDist) {
hitSomething = true;
} else if (dist < minDist * 3) {
nearMiss = true;
const intensity = 1 - (dist - minDist) / (minDist * 2);
if (intensity > nearMissIntensity) nearMissIntensity = intensity;
}
}
// Check canyon walls (simple distance from center)
// This approximates the sine-wave walls
const canyonWidth = 35;
const wallDistLeft = Math.abs(playerPos.x + canyonWidth / 2);
const wallDistRight = Math.abs(playerPos.x - canyonWidth / 2);
if (wallDistLeft < 5 || wallDistRight < 5) {
hitSomething = true;
} else if (wallDistLeft < 15) {
nearMiss = true;
const intensity = 1 - (wallDistLeft - 5) / 10;
if (intensity > nearMissIntensity) nearMissIntensity = intensity;
} else if (wallDistRight < 15) {
nearMiss = true;
const intensity = 1 - (wallDistRight - 5) / 10;
if (intensity > nearMissIntensity) nearMissIntensity = intensity;
}
// Check orbs
for (const orb of this.orbs) {
if (orb.collected) continue;
const dx = playerPos.x - orb.position.x;
const dy = playerPos.y - orb.position.y;
const dz = playerPos.z - orb.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < player.boundingRadius + orb.radius) {
orb.collected = true;
orb.mesh.visible = false;
orb.light.visible = false;
// Recharge energy
player.energy += 25;
if (player.energy > 100) player.energy = 100;
// Add to turbo
player.turbo += 15;
if (player.turbo > 100) player.turbo = 100;
audio.playCollect();
}
}
return { hitSomething, nearMiss, nearMissIntensity };
}
}
// ============== CAMERA CONTROLLER ==============
class CameraController {
constructor() {
this.baseFov = 70;
this.targetFov = this.baseFov;
this.currentFov = this.baseFov;
this.shakeAmount = 0;
this.shakeDecay = 0.9;
}
update(player, deltaTime) {
// Position behind and above player
const targetPos = new THREE.Vector3(
player.position.x - player.currentBank * 2,
player.position.y + 3 - player.currentPitch * 2,
player.position.z - 10
);
camera.position.lerp(targetPos, 0.05);
// Look at a point ahead of player
const lookTarget = new THREE.Vector3(
player.position.x + player.currentBank * 3,
player.position.y - player.currentPitch * 3,
player.position.z + 15
);
camera.lookAt(lookTarget);
// FOV for speed effect
this.targetFov = this.baseFov + player.speed * 8 + (player.isBoosting ? 15 : 0);
this.currentFov += (this.targetFov - this.currentFov) * 0.1;
camera.fov = this.currentFov;
camera.updateProjectionMatrix();
// Camera shake
if (this.shakeAmount > 0.001) {
camera.position.x += (Math.random() - 0.5) * this.shakeAmount;
camera.position.y += (Math.random() - 0.5) * this.shakeAmount;
this.shakeAmount *= this.shakeDecay;
}
}
addShake(amount) {
this.shakeAmount += amount;
if (this.shakeAmount > 1) this.shakeAmount = 1;
}
}
// ============== HUD ==============
function updateHUD(player, currentScore) {
document.getElementById('score').textContent = Math.floor(currentScore).toLocaleString();
const multiplierEl = document.getElementById('multiplier');
multiplierEl.textContent = `x${player.multiplier.toFixed(1)}`;
const speedPercent = (player.speed / player.maxSpeed) * 100;
document.getElementById('speed-fill').style.width = `${Math.min(speedPercent, 100)}%`;
document.getElementById('speed-label').textContent = `SPEED: ${Math.floor(player.speed * 100)}`;
document.getElementById('energy-fill').style.width = `${player.energy}%`;
// Change energy color based on level
const energyFill = document.getElementById('energy-fill');
if (player.energy < 25) {
energyFill.style.background = 'linear-gradient(90deg, #ff3366, #ff5050)';
} else if (player.energy < 50) {
energyFill.style.background = 'linear-gradient(90deg, #ff9632, #ffcc00)';
} else {
energyFill.style.background = 'linear-gradient(90deg, #00ffaa, #00aaff)';
}
document.getElementById('turbo-fill').style.height = `${player.turbo}%`;
}
function showGameOver(score, speed, mult) {
document.getElementById('game-over-screen').style.display = 'flex';
document.getElementById('final-score').textContent = Math.floor(score).toLocaleString();
document.getElementById('final-speed').textContent = Math.floor(speed * 100);
document.getElementById('final-multiplier').textContent = `x${mult.toFixed(1)}`;
currentState = GameState.GAMEOVER;
}
function hideScreens() {
document.getElementById('start-screen').style.display = 'none';
document.getElementById('game-over-screen').style.display = 'none';
}
// ============== MAIN GAME ==============
let player, world, particles, camController;
function initGame() {
if (player) {
player.reset();
world.reset();
} else {
player = new Player();
world = new WorldGenerator();
particles = new ParticleSystem();
camController = new CameraController();
}
score = 0;
maxSpeed = 0;
maxMultiplier = 1.0;
audio.init();
hideScreens();
currentState = GameState.PLAYING;
}
// Event listeners
document.getElementById('start-btn').addEventListener('click', initGame);
document.getElementById('restart-btn').addEventListener('click', initGame);
// Background stars
function createStars() {
const starGeo = new THREE.BufferGeometry();
const positions = new Float32Array(1000 * 3);
for (let i = 0; i < 1000; i++) {
positions[i * 3] = (Math.random() - 0.5) * 1000;
positions[i * 3 + 1] = Math.random() * 200 + 50;
positions[i * 3 + 2] = (Math.random() - 0.5) * 2000;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const starMat = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.5,
transparent: true,
opacity: 0.8
});
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
return stars;
}
const stars = createStars();
// Sun/atmosphere
function createSun() {
const sunGeo = new THREE.CircleGeometry(15, 32);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xff9966 });
const sun = new THREE.Mesh(sunGeo, sunMat);
sun.position.set(80, 80, -200);
// Glow
const glowGeo = new THREE.CircleGeometry(25, 32);
const glowMat = new THREE.MeshBasicMaterial({
color: 0xff6644,
transparent: true,
opacity: 0.3
});
const glow = new THREE.Mesh(glowGeo, glowMat);
sun.add(glow);
scene.add(sun);
return sun;
}
const sun = createSun();
// ============== GAME LOOP ==============
let lastTime = performance.now();
let multiplierPulseTimeout = null;
function animate() {
requestAnimationFrame(animate);
const currentTime = performance.now();
const deltaTime = (currentTime - lastTime) / 1000;
lastTime = currentTime;
// Limit delta to prevent huge jumps
const dt = Math.min(deltaTime, 0.05);
if (currentState === GameState.PLAYING) {
// Update player
const alive = player.update(dt, world.obstacles, world.orbs);
if (!alive) {
audio.playCrash();
showGameOver(score, maxSpeed, maxMultiplier);
return;
}
// Update world
world.update(player.position, score);
// Collision detection
const collision = world.checkCollisions(player);
if (collision.hitSomething) {
audio.playCrash();
showGameOver(score, maxSpeed, maxMultiplier);
return;
}
if (collision.nearMiss) {
// Increase multiplier based on proximity
player.multiplier += collision.nearMissIntensity * 0.02;
camController.addShake(collision.nearMissIntensity * 0.3);
// Visual pulse
const multiplierEl = document.getElementById('multiplier');
multiplierEl.classList.add('pulse');
if (multiplierPulseTimeout) clearTimeout(multiplierPulseTimeout);
multiplierPulseTimeout = setTimeout(() => {
multiplierEl.classList.remove('pulse');
}, 100);
// Sound
if (Math.random() < 0.1) {
audio.playNearMiss(collision.nearMissIntensity);
}
}
// Track max values
if (player.speed > maxSpeed) maxSpeed = player.speed;
if (player.multiplier > maxMultiplier) maxMultiplier = player.multiplier;
// Score
score += player.speed * player.multiplier * dt * 50;
// Particles
if (Math.random() < 0.5) {
particles.emitTrail(player);
}
particles.update(dt, player.speed, player.position);
// Camera
camController.update(player, dt);
// Audio
audio.update(player.speed, player.isBoosting);
// HUD
updateHUD(player, score);
}
// Render
renderer.render(scene, camera);
}
// Handle resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start animation loop
animate();
</script>
</body>
</html>
<!-- agent-meta {"model":"bytedance-seed/seed-2.0-code","provider":"openrouter","persona":"full","sessionId":"87b519ee-9805-4888-978c-68cd981c61d1","tokensIn":776179,"tokensOut":19115,"tokensTotal":795294,"cost":0.4454345,"turns":22,"toolCalls":21,"failedToolCalls":1,"timestamp":"2026-08-15T21:40:50.689Z"} -->