Canyon Run
Qwen 3.5 9b · composer

Reviews & generation details
Reviews
Human review · Jul 17, 2026 · rubric 1
Historical path-based review. Association with these exact bytes is unverified.
The reviewer marked this result as broken. It remains in the archive.
- Visual design
- Unscored
- UX / feel
- Unscored
- Works
- Unscored
- Prompt spirit
- Unscored
- Delight
- Unscored
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 42,374 bytes · SHA-256 a80a02c42537
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>High-Speed Procedural Canyon Racer</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.min.js"></script>
<style>
/* ── css: hud ── */
#hud {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 30px;
font-family: 'Arial', sans-serif;
z-index: 100;
pointer-events: none;
}
.energy-bar-container {
width: 200px;
height: 16px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 8px;
overflow: hidden;
}
.energy-fill {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #00ffff, #ff00ff);
transition: width 0.3s ease;
box-shadow: 0 0 10px rgba(0, 255, 255, 0.5);
}
.speedometer {
font-size: 48px;
font-weight: bold;
color: rgba(255, 255, 255, 0.9);
text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
min-width: 80px;
text-align: center;
}
.multiplier {
font-size: 72px;
font-weight: bold;
color: rgba(255, 107, 87, 0.95);
text-shadow: 0 0 30px rgba(255, 107, 87, 0.6);
min-width: 100px;
text-align: center;
transition: transform 0.1s ease;
}
.multiplier.pulse {
transform: scale(1.3) rotate(-5deg);
}
/* ── css: world ── */
#app {
width: 100vw;
height: 100vh;
position: fixed;
overflow: hidden;
}
canvas {
display: block;
outline: none;
}
/* Bloom and post-processing simulated */
.bloom-effect {
mix-blend-mode: screen;
}
/* Particle trail effects */
.trail-particle {
position: absolute;
pointer-events: none;
}
/* ── css: game-over ── */
.game-over-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(26, 10, 46, 0.9);
z-index: 1000;
font-family: 'Arial', sans-serif;
}
.game-over-overlay h1 {
animation: shake 0.5s ease-in-out infinite;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px) rotate(-2deg); }
75% { transform: translateX(10px) rotate(2deg); }
}
#game-over {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(180deg,
rgba(26, 10, 46, 0.95) 0%,
rgba(61, 37, 73, 0.95) 50%,
rgba(201, 79, 104, 0.9) 100%
);
z-index: 1000;
}
#game-over h1 {
font-size: 64px;
color: #ff6b57;
text-shadow: 0 0 30px rgba(255, 107, 87, 0.8);
margin-bottom: 30px;
}
#game-over p {
font-size: 24px;
color: #fff;
text-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
margin: 10px 0;
}
#game-over strong {
font-size: 32px;
color: #00ffff;
text-shadow: 0 0 10px rgba(0, 255, 255, 0.6);
}
#game-over button {
margin-top: 20px;
padding: 18px 45px;
font-size: 22px;
background: linear-gradient(90deg, #ff6b57, #c94f68);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
box-shadow: 0 0 25px rgba(255, 107, 87, 0.7);
transition: transform 0.2s, box-shadow 0.2s;
}
#game-over button:hover {
transform: scale(1.05);
box-shadow: 0 0 35px rgba(255, 107, 87, 0.9);
}
</style>
</head>
<body>
<div id="app"></div>
<div id="hud" class="hud">
<div class="energy-bar-container">
<div id="energy-fill" class="energy-fill"></div>
</div>
<div id="speedometer" class="speedometer">0</div>
<div id="multiplier" class="multiplier">x1</div>
</div>
<script>
'use strict';
// ── const: ENEMY_SPEED ──
const ENEMY_SPEED = 15;
// ── const: TUBE_RADIUS ──
const TUBE_RADIUS = 0.4;
// ── const: SEGMENT_LENGTH ──
const SEGMENT_LENGTH = 80;
// ── class: Player ──
class Player {
constructor(scene = null) {
this.mesh = new THREE.Group();
this.position = new THREE.Vector3(0, 0, 0);
this.velocity = new THREE.Vector3(0, 0, -15);
this.acceleration = new THREE.Vector3(0, 0, 0);
this.angularVelocity = new THREE.Vector3(0, 0, 0);
// Build the player craft geometry
const material = new THREE.MeshStandardMaterial({
color: 0xffffff,
roughness: 0.4,
metalness: 0.6
});
const fuselage = new THREE.ConeGeometry(0.5, 1.5, 8);
fuselage.rotateX(Math.PI / 2);
const mesh = new THREE.Mesh(fuselage, material);
this.mesh.add(mesh);
// Engine glow
const engineGeo = new THREE.ConeGeometry(0.2, 0.5, 8);
engineGeo.rotateX(-Math.PI / 2);
const engineMat = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.8
});
const engine = new THREE.Mesh(engineGeo, engineMat);
engine.position.z = -1;
this.mesh.add(engine);
// Add to scene if provided
if (scene) {
scene.add(this.mesh);
}
}
update(dt) {
this.velocity.add(this.acceleration.multiplyScalar(dt));
// Banking on turns - roll around Z axis
const lateralForce = Math.abs(this.velocity.x + this.velocity.y);
if (lateralForce > 0.1) {
this.mesh.rotation.z += lateralForce * 2.5 * dt;
}
}
applyInput(input, dt) {
// Pitch up/down for acceleration
if (input.pitch !== undefined) {
this.acceleration.z += input.pitch * 20 * dt;
}
// Roll/bank controls
if (input.roll !== undefined) {
this.mesh.rotation.x -= input.roll * 8 * dt;
this.mesh.rotation.y += input.roll * 6 * dt;
}
}
}
// ── class: PhysicsWorld ──
class PhysicsWorld {
constructor() {
this.gravity = new THREE.Vector3(0, -9.8, 0);
this.timeScale = 1;
}
applyGravity(player) {
player.acceleration.add(this.gravity.multiplyScalar(0.01));
}
applyDrag(player) {
const drag = new THREE.Vector3();
drag.set(-player.velocity.x, -player.velocity.y, -player.velocity.z);
drag.divideScalar(Math.max(player.velocity.length(), 0.1));
player.acceleration.add(drag.multiplyScalar(0.2));
}
}
// ── class: ProceduralGen ──
class ProceduralGen {
constructor(scene) {
this.scene = scene;
this.segments = [];
this.segmentCount = 0;
}
createSegment(zPosition) {
const segment = {
z: zPosition,
widthLeft: 25 + Math.random() * 30,
widthRight: 25 + Math.random() * 30,
heightTop: 40 + Math.random() * 60,
obstacles: [],
type: this.getSegmentType(zPosition),
age: 0
};
// Add canyon walls as geometry
const wallLeft = new THREE.Mesh(
new THREE.CylinderGeometry(segment.widthLeft/2, segment.widthLeft/1.8, SEGMENT_LENGTH + 20, 8),
this.getWallMaterial('left')
);
wallLeft.position.set(-segment.widthLeft/2 - (SEGMENT_LENGTH/2) + 10, 30, zPosition);
const wallRight = new THREE.Mesh(
new THREE.CylinderGeometry(segment.widthRight/2, segment.widthRight/1.8, SEGMENT_LENGTH + 20, 8),
this.getWallMaterial('right')
);
wallRight.position.set(segment.widthRight/2 + (SEGMENT_LENGTH/2) - 10, 30, zPosition);
segment.walls = [wallLeft, wallRight];
segment.mesh = new THREE.Group();
segment.mesh.add(wallLeft);
segment.mesh.add(wallRight);
this.scene.add(segment.mesh);
// Add obstacles based on type
this.addObstacles(segment);
return segment;
}
getSegmentType(z) {
const difficultyMultiplier = 1 + Math.min(Math.abs(z) / 5000, 2);
const rand = Math.random();
if (rand < 0.2 * difficultyMultiplier) {
return 'squeeze';
} else if (rand < 0.5 * difficultyMultiplier) {
return 'monoliths';
}
return 'standard';
}
getWallMaterial(side) {
const colors = [
0xff4422, 0x8B3A3A, 0x5C4033,
0xD2691E, 0xFF7F50, 0xCD853F
];
const color = colors[Math.floor(Math.random() * colors.length)];
return new THREE.MeshStandardMaterial({
color: color,
roughness: 0.3,
metalness: 0.2,
flatShading: true
});
}
addObstacles(segment) {
const numObstacles = Math.floor(Math.random() * 3) + 1;
for (let i = 0; i < numObstacles; i++) {
const obstacleType = this.getRandomObstacleType();
let mesh, geometry, material, position;
switch(obstacleType) {
case 'monolith':
const height = 15 + Math.random() * 20;
const radius = 3 + Math.random() * 4;
geometry = new THREE.CylinderGeometry(radius/2, radius, height, 6);
material = new THREE.MeshStandardMaterial({
color: 0x666666,
roughness: 0.5,
metalness: 0.3,
flatShading: true
});
mesh = new THREE.Mesh(geometry, material);
const angle = Math.random() * Math.PI * 2;
const x = (segment.widthLeft + segment.widthRight) / 4 * Math.cos(angle) + (SEGMENT_LENGTH/2);
const y = height / 2;
position = new THREE.Vector3(x, y, segment.z - SEGMENT_LENGTH/2 + 15);
break;
case 'debris':
const debrisSize = 1 + Math.random() * 3;
geometry = new THREE.BoxGeometry(debrisSize, debrisSize * 0.6, debrisSize);
material = new THREE.MeshStandardMaterial({
color: 0x444444,
roughness: 0.8,
metalness: 0.1,
flatShading: true
});
mesh = new THREE.Mesh(geometry, material);
const dx = (Math.random() - 0.5) * 30;
const dy = 2 + Math.random() * 15;
position = new THREE.Vector3(dx, dy, segment.z - SEGMENT_LENGTH/2 + Math.random() * SEGMENT_LENGTH);
break;
case 'floating':
geometry = new THREE.TorusGeometry(4, 0.8, 8, 16);
material = new THREE.MeshStandardMaterial({
color: 0x777777,
roughness: 0.3,
metalness: 0.5,
flatShading: true
});
mesh = new THREE.Mesh(geometry, material);
const fx = (Math.random() - 0.5) * (segment.widthLeft + segment.widthRight) / 2;
const fy = 8 + Math.random() * 12;
position = new THREE.Vector3(fx, fy, segment.z - SEGMENT_LENGTH/2);
break;
}
if (mesh) {
mesh.position.copy(position);
this.scene.add(mesh);
segment.mesh.add(mesh);
segment.obstacles.push({ mesh: mesh, type: obstacleType });
}
}
}
getRandomObstacleType() {
const types = ['monolith', 'debris', 'floating'];
return types[Math.floor(Math.random() * types.length)];
}
}
// ── class: EnergySystem ──
class EnergySystem {
constructor(scene) {
this.scene = scene;
this.orbs = [];
this.spawnDistance = 800;
}
spawnOrb(playerPosition, segment) {
const angle = Math.random() * Math.PI * 2;
const radius = (segment.widthLeft + segment.widthRight) / 3;
let x, y, z;
if (Math.random() > 0.5) {
x = -radius/1.5 + (Math.random() - 0.5);
} else {
x = radius/1.5 + (Math.random() - 0.5);
}
y = 2 + Math.random() * 15;
z = segment.z - SEGMENT_LENGTH/2 + Math.random() * SEGMENT_LENGTH;
const color = this.getOrbColor();
const glowIntensity = 0.6 + Math.random() * 0.4;
const geometry = new THREE.IcosahedronGeometry(0.6, 1);
const material = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: glowIntensity
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, y, z);
// Add point light for glow
const light = new THREE.PointLight(color, 1, 20);
light.position.copy(mesh.position);
this.scene.add(light);
const orb = {
mesh: mesh,
light: light,
radius: 0.5,
age: Math.random() * SEGMENT_LENGTH + 40
};
this.orbs.push(orb);
return orb;
}
getOrbColor() {
const colors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00];
return colors[Math.floor(Math.random() * colors.length)];
}
update(playerPosition) {
for (let i = this.orbs.length - 1; i >= 0; i--) {
const orb = this.orbs[i];
const distance = playerPosition.distanceTo(orb.mesh.position);
if (distance < orb.radius + TUBE_RADIUS) {
this.onCollect(orb);
this.scene.remove(orb.mesh);
this.scene.remove(orb.light);
this.orbs.splice(i, 1);
} else {
// Move with world
const dz = orb.age * 0.5;
orb.mesh.position.z += dz;
orb.light.position.copy(orb.mesh.position);
// Rotate
orb.mesh.rotation.y += 2 * dt;
orb.mesh.rotation.x += 1 * dt;
}
}
}
onCollect(orb) {
this.onRecharge();
}
onRecharge() {
// Reset energy - boost player forward
gameState.player.velocity.z = THREE.MathUtils.lerp(
gameState.player.velocity.z,
35,
0.2
);
}
}
// ── class: ParticleSystem ──
class ParticleSystem {
constructor(scene) {
this.scene = scene;
this.particles = [];
this.trailParticles = [];
this.debrisCount = 0;
// Create debris for speed illusion
for (let i = 0; i < 80; i++) {
const size = Math.random() * 0.3 + 0.1;
const geometry = new THREE.BoxGeometry(size, size, size);
const material = new THREE.MeshBasicMaterial({
color: 0x444444,
transparent: true,
opacity: 0.6
});
const mesh = new THREE.Mesh(geometry, material);
const x = (Math.random() - 0.5) * 200;
const y = (Math.random() - 0.5) * 100 + 30;
const z = (Math.random() - 0.5) * 200;
mesh.position.set(x, y, z);
const particle = {
mesh: mesh,
velocity: new THREE.Vector3(
(Math.random() - 0.5) * 3,
(Math.random() - 0.5) * 2 + 1,
(Math.random() - 0.5) * 2
),
age: 0,
life: Math.random() * 100 + 50
};
this.scene.add(mesh);
this.particles.push(particle);
}
}
update(playerPosition, dt) {
// Trail particles
if (Math.random() > 0.3) {
const size = 0.2 + Math.random() * 0.4;
const geometry = new THREE.ConeGeometry(size, size * 2, 6);
geometry.rotateX(-Math.PI / 2);
const material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.7
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.copy(playerPosition);
const playerZSpeed = gameState.player.velocity.z * 0.5;
mesh.position.z -= playerZSpeed;
this.scene.add(mesh);
this.trailParticles.push({ mesh: mesh, age: 0 });
}
// Move debris and remove off-screen
for (let i = this.particles.length - 1; i >= 0; i--) {
const p = this.particles[i];
p.mesh.position.add(p.velocity.clone().multiplyScalar(dt));
p.age += dt * 60;
if (p.age > p.life) {
this.scene.remove(p.mesh);
this.particles.splice(i, 1);
}
}
// Update trail particles
for (let i = this.trailParticles.length - 1; i >= 0; i--) {
const t = this.trailParticles[i];
t.age += dt * 60;
t.mesh.material.opacity = THREE.MathUtils.clamp(0.7 * (1 - t.age / 5), 0, 0.7);
if (t.age > 5) {
this.scene.remove(t.mesh);
this.trailParticles.splice(i, 1);
}
}
}
}
// ── class: ScoringSystem ──
class ScoringSystem {
constructor() {
this.multiplier = 1;
this.maxMultiplier = 50;
this.proximityScore = 0;
this.distanceTraveled = 0;
this.boostMeter = 0;
this.maxBoost = 100;
}
checkProximity(playerPosition, segment) {
const closestWallDistance = Math.min(
playerPosition.position.x + TUBE_RADIUS,
segment.widthLeft - TUBE_RADIUS/2,
(segment.widthRight - TUBE_RADIUS/2) - (playerPosition.position.x + TUBE_RADIUS),
999
);
const minSafeDistance = 1.5;
if (closestWallDistance < minSafeDistance) {
this.proximityScore += (minSafeDistance - closestWallDistance) * 20;
// Camera shake intensity based on danger
this.cameraShakeIntensity = Math.max(this.cameraShakeIntensity,
(minSafeDistance - closestWallDistance) * 5);
} else {
this.proximityScore *= 0.98;
if (this.proximityScore < 1) this.proximityScore = 1;
}
// Update multiplier based on proximity score
const dangerLevel = Math.min(1, (20 - closestWallDistance) / 20);
this.multiplier += dangerLevel * 2;
}
updateSpeed(speed) {
// Build boost from speed and distance
if (speed > 35) {
this.boostMeter = Math.min(this.boostMeter + 1, this.maxBoost);
} else {
this.boostMeter = Math.max(this.boostMeter - 0.5, 0);
}
// FOV widening based on boost
const baseFOV = 75;
const maxFOV = 110;
this.currentFOV = baseFOV + (maxFOV - baseFOV) * (this.boostMeter / this.maxBoost);
}
reset() {
this.multiplier = 1;
this.proximityScore = 0;
this.distanceTraveled = 0;
this.boostMeter = 0;
this.cameraShakeIntensity = 0;
this.currentFOV = 75;
}
onCollect() {
// Bonus multiplier for collecting items while risky
if (this.proximityScore > 10) {
this.multiplier += 2 + Math.random();
}
}
}
// ── class: CameraControl ──
class CameraControl {
constructor() {
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.cameraPosition = new THREE.Vector3(0, 5, -20);
// Smooth camera follow
this.targetPosition = new THREE.Vector3();
this.smoothFactor = 0.1;
}
update(player) {
// Calculate ideal camera position based on player movement
const lookAhead = -player.velocity.z * 2;
const xOffset = player.mesh.rotation.y * 5;
this.targetPosition.set(
player.position.x + xOffset,
player.position.y + 3,
player.position.z + lookAhead
);
// Smooth interpolation
this.cameraPosition.lerp(this.targetPosition, this.smoothFactor);
this.camera.position.copy(this.cameraPosition);
this.camera.lookAt(
player.position.x,
player.position.y,
player.position.z + 50
);
}
applyShake(shakeIntensity) {
if (shakeIntensity > 0.01) {
const shakeAmount = THREE.MathUtils.clamp(shakeIntensity, 0, 2);
this.cameraPosition.x += (Math.random() - 0.5) * shakeAmount;
this.cameraPosition.y += (Math.random() - 0.5) * shakeAmount;
}
}
applyBoostFOV(fovIncrease) {
const baseFOV = this.camera.fov;
const maxFOV = Math.min(140, baseFOV + fovIncrease);
const newFOV = THREE.MathUtils.lerp(baseFOV, maxFOV, 0.5);
this.camera.fov = newFOV;
}
}
// ── class: InputHandler ──
class InputHandler {
constructor() {
this.keys = {};
this.mouseX = 0;
this.mouseY = 0;
// Mouse listeners
window.addEventListener('mousemove', (e) => {
const rect = document.getElementById('hud').getBoundingClientRect();
this.mouseX = e.clientX - rect.left;
this.mouseY = e.clientY - rect.top;
});
window.addEventListener('keydown', (e) => {
this.keys[e.code] = true;
});
window.addEventListener('keyup', (e) => {
this.keys[e.code] = false;
});
// Touch listeners for mobile
const hud = document.getElementById('hud');
if (hud) {
hud.addEventListener('touchstart', (e) => {
const rect = e.touches[0].target.getBoundingClientRect();
this.touchX = e.touches[0].clientX - rect.left;
this.touchY = e.touches[0].clientY - rect.top;
}, { passive: true });
}
}
getPitch() {
// Mouse Y controls pitch (up/down)
const sensitivity = 3;
return -(this.mouseY / window.innerHeight - 0.5) * sensitivity;
}
getRoll() {
// Mouse X controls roll/bank (left/right)
const sensitivity = 2.5;
return this.mouseX / window.innerWidth - 0.5;
}
isPitchingUp() {
return this.keys['ArrowUp'] || this.keys['KeyW'] || this.keys['KeyE'];
}
isPitchingDown() {
return this.keys['ArrowDown'] || this.keys['KeyS'] || this.keys['KeyQ'];
}
getKeyboardRoll() {
if (this.keys['ArrowLeft'] || this.keys['KeyA']) return -0.8;
if (this.keys['ArrowRight'] || this.keys['KeyD']) return 0.8;
return 0;
}
clearKeys() {
for (let key in this.keys) {
this.keys[key] = false;
}
}
}
// ── class: World ──
class World {
constructor() {
this.scene = new THREE.Scene();
// Use hex color instead of gradient (Three.js doesn't have LinearGradient in older versions)
const sunsetColor = 0xff6b57;
this.scene.background = new THREE.Color(sunsetColor);
this.scene.fog = new THREE.FogExp2(sunsetColor, 0.015);
// Ambient light
const ambient = new THREE.AmbientLight(0xffffff, 0.3);
this.scene.add(ambient);
// Directional light (sun) - golden hour lighting
const sunLight = new THREE.DirectionalLight(0xffaa88, 1.2);
sunLight.position.set(100, 150, 100);
this.scene.add(sunLight);
// Add shadows for canyon walls
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 1024;
sunLight.shadow.mapSize.height = 1024;
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
}
addLights() {
// Add rim light for dramatic effect
const rimLight = new THREE.DirectionalLight(0x6644aa, 0.6);
rimLight.position.set(-50, -50, 100);
this.scene.add(rimLight);
// Add fill light from below
const fillLight = new THREE.PointLight(0xff6b57, 0.5, 200);
fillLight.position.set(0, -30, -50);
this.scene.add(fillLight);
}
}
// ── class: Game ──
class Game {
constructor() {
this.world = new World();
this.scene = this.world.scene;
this.camera = this.world.camera;
// Create Three.js renderer
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('app').appendChild(renderer.domElement);
// Store renderer for use later
this.renderer = renderer;
// Setup game state
this.player = new Player();
this.physics = new PhysicsWorld();
this.proceduralGen = new ProceduralGen(this.scene);
this.energySystem = new EnergySystem(this.scene);
this.particleSystem = new ParticleSystem(this.scene);
this.scoring = new ScoringSystem();
this.cameraControl = new CameraControl();
this.input = new InputHandler();
// Game parameters
this.speed = 25;
this.maxSpeed = 60;
this.boostActive = false;
this.energyDecay = 0.3;
this.distance = 0;
// Camera shake
this.cameraShakeIntensity = 0;
this.sound = new SoundSynthesizer();
this.hudPatcher = new HUDPatcher(this);
}
init() {
this.sound.startEngine();
for (let i = 0; i < 5; i++) {
const zPos = -i * SEGMENT_LENGTH;
this.proceduralGen.createSegment(zPos);
}
// Initial energy orb
setTimeout(() => {
const firstSegment = this.proceduralGen.segments[0];
if (firstSegment && firstSegment.mesh) {
this.energySystem.spawnOrb(this.player.position, firstSegment);
}
}, 100);
}
spawnEnergyOrb() {
const segment = this.proceduralGen.segments[this.proceduralGen.segments.length - 1];
if (segment && segment.mesh) {
const orb = this.energySystem.spawnOrb(this.player.position, segment);
return orb;
}
return null;
}
spawnDebris() {
const segment = this.proceduralGen.segments[Math.floor(Math.random() * this.proceduralGen.segments.length)];
if (!segment || !segment.mesh) return null;
const geometry = new THREE.BoxGeometry(1.5, 1, 1.5);
const material = new THREE.MeshStandardMaterial({
color: 0x555555,
roughness: 0.7,
metalness: 0.2,
flatShading: true
});
const mesh = new THREE.Mesh(geometry, material);
const x = (Math.random() - 0.5) * 40;
const y = 3 + Math.random() * 20;
const z = segment.z - SEGMENT_LENGTH/2 + Math.random() * SEGMENT_LENGTH;
mesh.position.set(x, y, z);
this.scene.add(mesh);
segment.mesh.add(mesh);
return mesh;
}
applyPlayerControls(dt) {
this.input.clearKeys();
const pitch = this.input.getPitch();
const roll = this.input.getRoll() + this.input.getKeyboardRoll();
if (pitch !== undefined || roll !== undefined) {
this.player.applyInput({ pitch, roll }, dt);
}
}
update(dt) {
// Apply gravity and drag
this.physics.applyGravity(this.player);
this.physics.applyDrag(this.player);
// Update player movement
this.player.update(dt);
// Move player forward based on speed
const currentSpeed = Math.abs(this.player.velocity.z);
this.speed = THREE.MathUtils.lerp(this.speed, currentSpeed * 1.2, 0.1);
// Boost effect - check if scoring has boost meter
const boostThreshold = this.scoring.maxBoost * 0.8;
if (this.scoring.boostMeter > boostThreshold) {
this.boostActive = true;
this.player.velocity.z *= 1.15;
this.energyDecay *= 2;
} else {
this.boostActive = false;
}
// Update camera
this.cameraControl.update(this.player);
this.cameraControl.applyShake(this.cameraShakeIntensity);
if (this.scoring.boostMeter > this.scoring.maxBoost * 0.9) {
this.cameraControl.applyBoostFOV(40);
}
// Update energy decay based on boost state
this.energyDecay = THREE.MathUtils.lerp(
this.energyDecay,
this.energyDecay + (this.boostActive ? 0.1 : -0.05),
0.1
);
// Spawn new segments as player moves forward
const playerZ = this.player.position.z;
const lastSegment = this.proceduralGen.segments[this.proceduralGen.segments.length - 1];
if (lastSegment && lastSegment.mesh) {
const lastZ = lastSegment.z;
if (playerZ < lastZ + SEGMENT_LENGTH * 2) {
const newZ = lastZ + SEGMENT_LENGTH;
this.proceduralGen.createSegment(newZ);
}
}
// Remove old segments
while (this.proceduralGen.segments[0]) {
const first = this.proceduralGen.segments[0];
if (first.z > playerZ + SEGMENT_LENGTH * 3) {
if (first.mesh) this.scene.remove(first.mesh);
for (let j = 0; j < first.walls.length && first.walls[j]; j++) {
this.scene.remove(first.walls[j]);
}
this.proceduralGen.segments.shift();
} else {
break;
}
}
// Spawn energy orbs randomly
if (Math.random() < 0.01 && this.proceduralGen.segments.length > 2) {
const lastSegment = this.proceduralGen.segments[this.proceduralGen.segments.length - 1];
if (lastSegment && lastSegment.mesh) {
this.energySystem.spawnOrb(this.player.position, lastSegment);
}
}
// Spawn debris for speed illusion
if (Math.random() < 0.05) {
this.spawnDebris();
}
// Update systems
this.energySystem.update(this.player.position);
this.particleSystem.update(this.player.position, dt);
// Check collisions with debris
this.checkCollisions();
// Apply controls
this.applyPlayerControls(dt);
}
checkCollisions() {
const playerBox = new THREE.Box3().setFromObject(this.player.mesh);
for (let i = 0; i < this.proceduralGen.segments.length; i++) {
const segment = this.proceduralGen.segments[i];
if (!segment) continue;
// Check wall collisions
for (let j = 0; j < segment.walls.length && segment.walls[j]; j++) {
const wall = segment.walls[j];
const wallBox = new THREE.Box3().setFromObject(wall);
if (playerBox.intersectsBox(wallBox)) {
this.handleCollision();
}
}
// Check obstacle collisions
for (let k = 0; k < segment.obstacles.length; k++) {
const obs = segment.obstacles[k];
if (!obs.mesh) continue;
const obsBox = new THREE.Box3().setFromObject(obs.mesh);
if (playerBox.intersectsBox(obsBox)) {
this.handleCollision();
}
}
}
}
handleCollision() {
// Game over on collision
gameOver(0);
}
render() {
const renderer = this.renderer;
// Camera shake interpolation
this.cameraShakeIntensity = THREE.MathUtils.lerp(
this.cameraShakeIntensity, 0, 0.15
);
renderer.render(this.scene, this.camera);
}
getGameState() {
return {
energy: this.player.velocity.z / this.maxSpeed * 100,
multiplier: this.scoring.multiplier,
speed: this.speed,
boostMeter: this.scoring.boostMeter
};
}
}
// ── let: gameState ──
// Game state for rendering updates
let gameState = null;
// ── class: HUDPatcher ──
class HUDPatcher {
constructor(game) {
this.game = game;
this.energyFill = document.getElementById('energy-fill');
this.speedometer = document.getElementById('speedometer');
this.multiplier = document.getElementById('multiplier');
}
update() {
const state = this.game.getGameState();
// Update energy bar
if (this.energyFill) {
this.energyFill.style.width = Math.max(0, state.energy) + '%';
// Change color based on energy level
if (state.energy > 50) {
this.energyFill.style.background = 'linear-gradient(90deg, #00ffff, #ff00ff)';
this.energyFill.style.boxShadow = '0 0 15px rgba(0, 255, 255, 0.8)';
} else if (state.energy > 20) {
this.energyFill.style.background = 'linear-gradient(90deg, #ffff00, #ff6b00)';
this.energyFill.style.boxShadow = '0 0 10px rgba(255, 107, 87, 0.6)';
} else {
this.energyFill.style.background = 'linear-gradient(90deg, #ff4400, #cc0000)';
this.energyFill.style.boxShadow = '0 0 5px rgba(255, 68, 32, 0.5)';
}
}
// Update speedometer
if (this.speedometer) {
const displaySpeed = Math.floor(this.game.speed);
this.speedometer.textContent = displaySpeed;
// Color based on speed
if (displaySpeed > 40) {
this.speedometer.style.color = '#ff6b35';
this.speedometer.style.textShadow = '0 0 20px rgba(255, 107, 53, 0.8)';
} else if (displaySpeed > 30) {
this.speedometer.style.color = '#ffff00';
this.speedometer.style.textShadow = '0 0 15px rgba(255, 255, 0, 0.6)';
} else {
this.speedometer.style.color = 'rgba(255, 255, 255, 0.9)';
this.speedometer.style.textShadow = '0 0 20px rgba(255, 255, 255, 0.5)';
}
}
// Update multiplier
if (this.multiplier) {
const multDisplay = Math.floor(this.game.scoring.multiplier);
this.multiplier.textContent = 'x' + multDisplay;
// Pulse effect on high multiplier
if (multDisplay > 15) {
this.multiplier.classList.add('pulse');
} else {
this.multiplier.classList.remove('pulse');
}
}
}
}
// ── class: SoundSynthesizer ──
class SoundSynthesizer {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.masterGain = this.ctx.createGain();
this.masterGain.gain.value = 0.3;
this.masterGain.connect(this.ctx.destination);
// Engine hum oscillator
this.engineOsc = null;
this.engineGain = null;
}
startEngine() {
if (this.ctx.state === 'suspended') {
this.ctx.resume();
}
// Low hum for engine
this.engineOsc = this.ctx.createOscillator();
this.engineGain = this.ctx.createGain();
this.engineOsc.type = 'sawtooth';
this.engineOsc.frequency.value = 50;
// Filter to smooth the sound
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 200;
this.engineOsc.connect(filter);
filter.connect(this.engineGain);
this.engineGain.connect(this.masterGain);
this.engineGain.gain.value = 0.1;
this.engineOsc.start();
}
stopEngine() {
if (this.engineOsc) {
this.engineOsc.stop();
}
}
update(engineSpeed) {
if (this.engineOsc) {
// Pitch modulate with speed
const targetFreq = 50 + engineSpeed / 3;
this.engineOsc.frequency.setTargetAtTime(targetFreq, this.ctx.currentTime, 0.1);
// Volume based on boost
if (this.game && this.game.scoring.boostActive) {
this.engineGain.gain.setTargetAtTime(0.2, this.ctx.currentTime, 0.1);
} else {
this.engineGain.gain.setTargetAtTime(0.1, this.ctx.currentTime, 0.1);
}
}
}
playCollectSound() {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(800, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1200, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(0.5, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.3);
}
playNearMissSound() {
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(150, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.4);
gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(this.masterGain);
osc.start();
osc.stop(this.ctx.currentTime + 0.4);
}
}
// ── let: dt ──
// Shared time delta variable
let dt = 0;
// ── main ──
// Global variables
let gameLoopId = null;
let lastTime = 0;
let score = 0;
let maxSpeed = 0;
let isGameOver = false;
// Start the game
function initGame() {
gameState = new Game();
// Initialize player position and velocity directly on mesh
const startPos = new THREE.Vector3(0, 0, 0);
gameState.player.position.copy(startPos);
gameState.player.velocity.set(0, 0, -25);
gameState.player.acceleration.set(0, 0, 0);
// Bind resize event
window.addEventListener('resize', () => {
gameState.camera.aspect = window.innerWidth / window.innerHeight;
gameState.camera.updateProjectionMatrix();
const renderer = document.querySelector('#app canvas');
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start animation loop
lastTime = performance.now();
requestAnimationFrame(gameLoop);
}
function gameLoop(timestamp) {
if (isGameOver || !gameState) return;
const currentTime = timestamp / 1000;
dt = currentTime - lastTime;
lastTime = currentTime;
// Cap dt to prevent huge jumps
if (dt > 0.1) {
dt = 0.1;
lastTime = currentTime - dt;
}
// Update game
gameState.update(dt);
gameState.hudPatcher.update();
gameState.render();
// Update score
const currentSpeed = Math.abs(gameState.player.velocity.z);
score += currentSpeed * 10 * dt;
maxSpeed = Math.max(maxSpeed, currentSpeed);
gameLoopId = requestAnimationFrame(gameLoop);
}
// Game over handler
function gameOver(finalScore) {
isGameOver = true;
cancelAnimationFrame(gameLoopId);
// Show game over screen
const hud = document.getElementById('hud');
if (hud) {
hud.style.display = 'none';
}
// Create game over overlay
const gameOverOverlay = document.createElement('div');
gameOverOverlay.id = 'game-over';
gameOverOverlay.className = 'game-over-overlay';
gameOverOverlay.innerHTML = `
<h1 style="font-size: 48px; color: #ff6b57; text-shadow: 0 0 20px rgba(255,107,87,0.8); margin-bottom: 20px;">CRASHED</h1>
<div style="font-size: 24px; color: #fff; text-shadow: 0 0 10px rgba(255,255,255,0.5);">
<p>Final Score: <strong style="color: #00ffff;">${Math.floor(score)}</strong></p>
<p>Max Speed: <strong style="color: #ffff00;">${maxSpeed.toFixed(1)} km/h</strong></p>
<button onclick="initGame()" style="margin-top: 20px; padding: 15px 40px; font-size: 20px; background: linear-gradient(90deg, #ff6b57, #c94f68); color: white; border: none; border-radius: 8px; cursor: pointer; box-shadow: 0 0 20px rgba(255,107,87,0.6);">RESTART</button>
</div>
`;
document.getElementById('app').appendChild(gameOverOverlay);
}
// Start on page load
initGame();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.5-9b","provider":"lmstudio","persona":"composer","sessionId":"9ef03626-3c5b-467d-aa05-03a027643d54","tokensIn":1609797,"tokensOut":36098,"tokensTotal":1645895,"turns":54,"toolCalls":53,"failedToolCalls":0,"timestamp":"2026-07-14T03:29:00.875Z"} -->