Canyon Run
GLM 4.7 Flash GGUF · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
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 34,862 bytes · SHA-256 ab4b495aa95b
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Racer - Procedural Flight</title>
<script src="https://unpkg.com/three@0.160.0/build/three.module.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/postprocessing/EffectComposer.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/postprocessing/RenderPass.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/postprocessing/UnrealBloomPass.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/postprocessing/ShaderPass.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/shaders/CopyShader.js"></script>
<script src="https://unpkg.com/three@0.160.0/examples/jsm/shaders/LuminosityHighPassShader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/postprocessing/OutputPass.js"></script>
<style>
/* ── css: hud ── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a0a;
overflow: hidden;
font-family: 'Courier New', monospace;
color: #fff;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
}
#game-canvas {
display: block;
width: 100%;
height: 100%;
}
/* HUD */
#hud {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 20px;
display: flex;
justify-content: space-between;
align-items: flex-start;
pointer-events: none;
z-index: 10;
}
.hud-panel {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 100, 50, 0.3);
border-radius: 4px;
padding: 10px 15px;
backdrop-filter: blur(5px);
}
.hud-speed {
display: flex;
flex-direction: column;
gap: 5px;
min-width: 100px;
}
.speed-label {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
}
.speed-bar {
width: 100%;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
.fill {
height: 100%;
background: linear-gradient(90deg, #ff6b35, #ffcc00);
width: 0%;
transition: width 0.1s linear;
}
.speed-num {
font-size: 18px;
font-weight: bold;
text-align: right;
color: #ffcc00;
text-shadow: 0 0 10px rgba(255, 200, 0, 0.5);
}
.hud-multiplier {
text-align: center;
}
.multiplier-label {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
display: block;
}
.multiplier-num {
font-size: 32px;
font-weight: bold;
color: #ff6b35;
text-shadow: 0 0 20px rgba(255, 107, 53, 0.6);
transition: transform 0.1s ease, color 0.2s ease;
}
.multiplier-num.pulse {
transform: scale(1.2);
color: #ffcc00;
}
.hud-energy {
display: flex;
flex-direction: column;
gap: 5px;
min-width: 100px;
text-align: right;
}
.energy-label {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
}
/* Overlays */
.overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.8);
backdrop-filter: blur(10px);
z-index: 20;
opacity: 1;
transition: opacity 0.5s ease;
}
.overlay.hidden {
opacity: 0;
pointer-events: none;
}
#start-screen h1 {
font-size: 48px;
color: #ffcc00;
text-shadow: 0 0 30px rgba(255, 200, 0, 0.4);
letter-spacing: 8px;
margin-bottom: 10px;
}
.subtitle {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 4px;
margin-bottom: 40px;
}
#start-btn, #restart-btn {
background: linear-gradient(135deg, #ff6b35, #ff4444);
border: none;
color: white;
padding: 15px 40px;
font-size: 16px;
font-family: 'Courier New', monospace;
letter-spacing: 3px;
cursor: pointer;
clip-path: polygon(10% 0, 100% 0, 100% 70%, 90% 100%, 0 100%, 0 30%);
transition: all 0.2s ease;
text-transform: uppercase;
}
#start-btn:hover, #restart-btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(255, 107, 53, 0.4);
}
.stats {
display: flex;
gap: 40px;
margin-bottom: 30px;
}
.stat {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-label {
font-size: 10px;
color: rgba(255, 255, 255, 0.5);
letter-spacing: 2px;
margin-bottom: 5px;
}
.stat-value {
font-size: 36px;
color: #ffcc00;
text-shadow: 0 0 20px rgba(255, 200, 0, 0.4);
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div id="app"><div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="hud">
<div class="hud-speed">
<div class="speed-label">SPEED</div>
<div class="speed-bar"><div id="speed-fill" class="fill"></div></div>
<div id="speed-value" class="speed-num">0</div>
</div>
<div class="hud-multiplier" id="multiplier-display">
<span class="multiplier-label">MULTIPLIER</span>
<span id="multiplier-value" class="multiplier-num">x1</span>
</div>
<div class="hud-energy">
<div class="energy-label">ENERGY</div>
<div class="energy-bar"><div id="energy-fill" class="fill"></div></div>
</div>
</div>
<div id="start-screen" class="overlay">
<h1>CANYON RACER</h1>
<p class="subtitle">Procedural Flight</p>
<button id="start-btn" class="btn">START ENGINE</button>
</div>
<div id="game-over-screen" class="overlay hidden">
<h2>CRITICAL FAILURE</h2>
<div class="stats">
<div class="stat">
<span class="stat-label">SCORE</span>
<span id="final-score" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">MAX SPEED</span>
<span id="final-speed" class="stat-value">0</span>
</div>
<div class="stat">
<span class="stat-label">NEAR MISSES</span>
<span id="final-misses" class="stat-value">0</span>
</div>
</div>
<button id="restart-btn" class="btn">RESTART</button>
</div>
</div></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./game":"src/game.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const game_1 = require("./game");
// Expose to window for debugging
window.gameState = game_1.STATE;
window.game = {
init: game_1.init,
startGame: game_1.startGame,
gameOver: game_1.gameOver,
updatePlayer: game_1.updatePlayer,
updateEnvironment: game_1.updateEnvironment,
checkCollisions: game_1.checkCollisions,
updateHUD: game_1.updateHUD,
};
// Start when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
(0, game_1.init)();
});
}
else {
(0, game_1.init)();
}
};
// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.trailIndex = exports.trailCapacity = exports.trailCount = exports.trailPositions = exports.trailGeometry = exports.trailSystem = exports.audioManager = exports.composer = exports.renderer = exports.camera = exports.scene = exports.INPUT = exports.orbs = exports.obstacles = exports.CHUNKS = exports.GAME_STATE = exports.STATE = exports.CONSTANTS = exports.AudioManager = void 0;
exports.generateChunk = generateChunk;
exports.createPlayerCraft = createPlayerCraft;
exports.initTrailSystem = initTrailSystem;
exports.updateTrail = updateTrail;
exports.addTrailPoint = addTrailPoint;
exports.updateCameraShake = updateCameraShake;
exports.checkCollisions = checkCollisions;
exports.initEnvironment = initEnvironment;
exports.updateEnvironmentArrays = updateEnvironmentArrays;
exports.generateAhead = generateAhead;
exports.cleanupBehind = cleanupBehind;
exports.updateEnvironment = updateEnvironment;
exports.updatePlayer = updatePlayer;
exports.updateHUD = updateHUD;
exports.gameOver = gameOver;
exports.startGame = startGame;
exports.gameLoop = gameLoop;
exports.setupInputs = setupInputs;
exports.handleResize = handleResize;
exports.init = init;
// Audio Context
const AudioContext = window.AudioContext || window.webkitAudioContext;
class AudioManager {
constructor() {
this.ctx = null;
this.engineOsc = null;
this.engineGain = null;
this.gainNode = null;
}
init() {
this.ctx = new AudioContext();
this.gainNode = this.ctx.createGain();
this.gainNode.connect(this.ctx.destination);
this.gainNode.gain.value = 0.3;
this.engineOsc = this.ctx.createOscillator();
this.engineOsc.type = 'sawtooth';
this.engineOsc.frequency.value = 60;
this.engineGain = this.ctx.createGain();
this.engineGain.gain.value = 0;
this.engineOsc.connect(this.engineGain);
this.engineGain.connect(this.gainNode);
this.engineOsc.start();
}
updateEngineSpeed(speed, maxSpeed) {
if (!this.ctx || !this.engineGain)
return;
const normalizedSpeed = Math.min(speed / maxSpeed, 1);
this.engineOsc.frequency.setTargetAtTime(60 + normalizedSpeed * 80, this.ctx.currentTime, 0.1);
this.engineGain.gain.setTargetAtTime(normalizedSpeed * 0.15, this.ctx.currentTime, 0.1);
}
playCollect() {
if (!this.ctx)
return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1760, this.ctx.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(this.gainNode);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
}
playNearMiss() {
if (!this.ctx)
return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(440, this.ctx.currentTime);
osc.frequency.linearRampToValueAtTime(660, this.ctx.currentTime + 0.3);
gain.gain.setValueAtTime(0.1, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
osc.connect(gain);
gain.connect(this.gainNode);
osc.start();
osc.stop(this.ctx.currentTime + 0.4);
}
playCrash() {
if (!this.ctx)
return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(50, this.ctx.currentTime + 0.5);
gain.gain.setValueAtTime(0.4, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.5);
osc.connect(gain);
gain.connect(this.gainNode);
osc.start();
osc.stop(this.ctx.currentTime + 0.5);
}
resume() {
if (this.ctx && this.ctx.state === 'suspended') {
this.ctx.resume();
}
}
}
exports.AudioManager = AudioManager;
// Constants
exports.CONSTANTS = {
PLAYER_SPEED: 80,
MAX_SPEED: 300,
ACCELERATION: 15,
FRICTION: 0.98,
ENERGY_DECAY: 8,
ENERGY_RECHARGE: 30,
BOOST_MULTIPLIER: 2,
NEAR_MISS_THRESHOLD: 3,
NEAR_MISS_BONUS: 100,
CHUNK_SIZE: 400,
CHUNK_GENERATION_DISTANCE: 2,
WALL_COLOR: 0x1a1a2e,
MONOLITH_COLOR: 0x4a4a6e,
SQUEEZE_COLOR: 0x3a3a5e,
ORB_COLOR: 0xffcc00,
PLAYER_COLOR: 0xff6b35,
TRAIL_COLOR: 0xff4444,
AMBIENT_LIGHT: 0x404060,
DIRECTIONAL_LIGHT: 0xffcc88,
};
exports.STATE = {
position: new THREE.Vector3(0, 0, 0),
velocity: new THREE.Vector3(0, 0, exports.CONSTANTS.PLAYER_SPEED),
rotation: new THREE.Euler(0, 0, 0),
energy: 100,
maxEnergy: 100,
multiplier: 1,
score: 0,
maxSpeed: exports.CONSTANTS.MAX_SPEED,
boost: 0,
boostAvailable: false,
nearMisses: 0,
isBoosting: false,
turboMeter: 0,
isPlaying: false,
};
exports.GAME_STATE = {
isPlaying: false,
speed: exports.CONSTANTS.PLAYER_SPEED,
distance: 0,
difficultyMultiplier: 1,
cameraShake: 0,
turboMeter: 0,
};
exports.CHUNKS = [];
exports.obstacles = [];
exports.orbs = [];
// Input State
exports.INPUT = {
left: false,
right: false,
up: false,
down: false,
boost: false,
};
exports.trailCount = 0;
exports.trailCapacity = 200;
exports.trailIndex = 0;
// Procedural Generation
function generateChunk(zPosition, difficulty) {
const chunk = {
obstacles: [],
orbs: [],
position: zPosition,
};
const width = 100 + difficulty * 20;
const center = 0;
const numObstacles = 3 + Math.floor(difficulty);
for (let i = 0; i < numObstacles; i++) {
const type = Math.random() < 0.5 ? 'wall' : 'monolith';
const x = center + (Math.random() - 0.5) * width;
const z = zPosition - 150 + Math.random() * 200;
if (type === 'wall') {
const size = 20 + Math.random() * 30 + difficulty * 5;
const geometry = new THREE.BoxGeometry(size, 80, 40);
const material = new THREE.MeshLambertMaterial({
color: Math.random() > 0.5 ? exports.CONSTANTS.WALL_COLOR : exports.CONSTANTS.MONOLITH_COLOR,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, 0, z);
mesh.castShadow = true;
mesh.receiveShadow = true;
const collider = new THREE.Box3().setFromObject(mesh);
chunk.obstacles.push({
type,
mesh,
collider,
scoreValue: 10,
});
}
else {
const size = 15 + Math.random() * 15 + difficulty * 3;
const geometry = new THREE.ConeGeometry(size * 0.7, size * 2, 8);
const material = new THREE.MeshLambertMaterial({
color: exports.CONSTANTS.MONOLITH_COLOR,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, -size * 0.5, z);
mesh.rotation.z = Math.PI / 2;
mesh.castShadow = true;
mesh.receiveShadow = true;
const collider = new THREE.Box3().setFromObject(mesh);
chunk.obstacles.push({
type,
mesh,
collider,
scoreValue: 15,
});
}
}
// Generate orbs
const numOrbs = 3 + Math.floor(difficulty * 0.5);
for (let i = 0; i < numOrbs; i++) {
const x = center + (Math.random() - 0.5) * width;
const z = zPosition - 150 + Math.random() * 200;
const geometry = new THREE.OctahedronGeometry(3);
const material = new THREE.MeshBasicMaterial({
color: exports.CONSTANTS.ORB_COLOR,
emissive: exports.CONSTANTS.ORB_COLOR,
emissiveIntensity: 2,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(x, 0, z);
const collider = new THREE.Sphere();
chunk.orbs.push({
mesh,
collider,
active: true,
});
}
return chunk;
}
// Player Craft
function createPlayerCraft() {
const group = new THREE.Group();
// Main body
const bodyGeometry = new THREE.ConeGeometry(1.5, 6, 6);
const bodyMaterial = new THREE.MeshPhongMaterial({
color: exports.CONSTANTS.PLAYER_COLOR,
flatShading: true,
shininess: 100,
});
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.rotation.x = Math.PI / 2;
group.add(body);
// Wings
const wingGeometry = new THREE.BoxGeometry(8, 0.2, 3);
const wingMaterial = new THREE.MeshPhongMaterial({
color: 0xcc4400,
flatShading: true,
});
const wings = new THREE.Mesh(wingGeometry, wingMaterial);
wings.position.set(0, 0, 1);
group.add(wings);
// Engine glow
const engineGeometry = new THREE.SphereGeometry(0.5, 8, 8);
const engineMaterial = new THREE.MeshBasicMaterial({
color: 0xffaa00,
emissive: 0xff6600,
emissiveIntensity: 3,
});
const engine = new THREE.Mesh(engineGeometry, engineMaterial);
engine.position.set(0, 0, 2.5);
group.add(engine);
// Engine particle emitter point
engine.particleOrigin = new THREE.Vector3(0, 0, 3);
group.position.copy(exports.STATE.position);
return group;
}
// Trail System
function initTrailSystem() {
exports.trailGeometry = new THREE.BufferGeometry();
exports.trailPositions = new Float32Array(exports.trailCapacity * 3);
exports.trailGeometry.setAttribute('position', new THREE.BufferAttribute(exports.trailPositions, 3));
const trailMaterial = new THREE.PointsMaterial({
color: exports.CONSTANTS.TRAIL_COLOR,
size: 0.3,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending,
});
exports.trailSystem = new THREE.Points(exports.trailGeometry, trailMaterial);
exports.trailSystem.frustumCulled = false;
exports.scene.add(exports.trailSystem);
}
function updateTrail() {
if (exports.trailCount === 0)
return;
const positions = exports.trailGeometry.attributes.position.array;
const fade = 0.95;
for (let i = 0; i < exports.trailCount; i++) {
const srcIdx = (exports.trailIndex + i) % exports.trailCapacity;
positions[srcIdx * 3] = exports.trailPositions[i * 3] * fade;
positions[srcIdx * 3 + 1] = exports.trailPositions[i * 3 + 1] * fade;
positions[srcIdx * 3 + 2] = exports.trailPositions[i * 3 + 2] * fade;
}
exports.trailGeometry.attributes.position.needsUpdate = true;
}
function addTrailPoint(position) {
const positions = exports.trailGeometry.attributes.position.array;
positions[exports.trailIndex * 3] = position.x;
positions[exports.trailIndex * 3 + 1] = position.y;
positions[exports.trailIndex * 3 + 2] = position.z;
exports.trailIndex = (exports.trailIndex + 1) % exports.trailCapacity;
exports.trailCount = Math.min(exports.trailCount + 1, exports.trailCapacity);
}
// Camera Shake
function updateCameraShake() {
if (exports.GAME_STATE.cameraShake <= 0)
return;
exports.GAME_STATE.cameraShake *= 0.9;
if (exports.GAME_STATE.cameraShake < 0.1) {
exports.GAME_STATE.cameraShake = 0;
return;
}
const offsetX = (Math.random() - 0.5) * exports.GAME_STATE.cameraShake;
const offsetY = (Math.random() - 0.5) * exports.GAME_STATE.cameraShake;
const offsetZ = (Math.random() - 0.5) * exports.GAME_STATE.cameraShake;
exports.camera.position.add(new THREE.Vector3(offsetX, offsetY, offsetZ));
}
// Collision Detection
function checkCollisions() {
const playerBox = new THREE.Box3();
const playerRadius = 2;
playerBox.min.set(exports.STATE.position.x - playerRadius, exports.STATE.position.y - playerRadius, exports.STATE.position.z - playerRadius);
playerBox.max.set(exports.STATE.position.x + playerRadius, exports.STATE.position.y + playerRadius, exports.STATE.position.z + playerRadius);
// Check obstacles
for (let i = exports.obstacles.length - 1; i >= 0; i--) {
const obstacle = exports.obstacles[i];
if (playerBox.intersectsBox(obstacle.collider)) {
gameOver();
return;
}
// Check near misses
const distance = obstacle.collider.center.distanceTo(exports.STATE.position);
if (distance < exports.CONSTANTS.NEAR_MISS_THRESHOLD && distance > 1) {
exports.STATE.multiplier = Math.min(exports.STATE.multiplier + 1, 5);
exports.STATE.score += exports.CONSTANTS.NEAR_MISS_BONUS * exports.STATE.multiplier;
exports.STATE.nearMisses++;
exports.GAME_STATE.cameraShake = Math.min(exports.GAME_STATE.cameraShake + 0.2, 1);
exports.obstacles.splice(i, 1);
}
}
// Check orbs
for (let i = exports.orbs.length - 1; i >= 0; i--) {
const orb = exports.orbs[i];
if (orb.active) {
const distance = orb.collider.center.distanceTo(exports.STATE.position);
if (exports.STATE.position.distanceTo(orb.mesh.position) < distance) {
orb.active = false;
orb.mesh.visible = false;
exports.STATE.energy = Math.min(exports.STATE.energy + exports.CONSTANTS.ENERGY_RECHARGE, exports.STATE.maxEnergy);
exports.STATE.score += 50;
exports.STATE.multiplier = Math.max(exports.STATE.multiplier - 1, 1);
exports.STATE.turboMeter = Math.min(exports.STATE.turboMeter + 20, 100);
exports.audioManager.playCollect();
}
}
}
}
// Procedural Environment
function initEnvironment() {
exports.scene = new THREE.Scene();
exports.scene.background = new THREE.Color(0x0a0a15);
exports.scene.fog = new THREE.Fog(0x0a0a15, 200, 600);
exports.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
exports.camera.position.set(0, 10, 30);
exports.camera.lookAt(exports.STATE.position.clone().add(new THREE.Vector3(0, 0, 100)));
exports.renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('game-canvas'),
antialias: true,
alpha: false,
});
exports.renderer.setSize(window.innerWidth, window.innerHeight);
exports.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
exports.renderer.shadowMap.enabled = true;
exports.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// Post-processing
const renderScene = new THREE.RenderPass(exports.scene, exports.camera);
const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
bloomPass.threshold = 0.2;
bloomPass.strength = 1.2;
bloomPass.radius = 0.5;
const outputPass = new THREE.OutputPass();
exports.composer = new THREE.EffectComposer(exports.renderer);
exports.composer.addPass(renderScene);
exports.composer.addPass(bloomPass);
exports.composer.addPass(outputPass);
// Lighting
const ambientLight = new THREE.AmbientLight(exports.CONSTANTS.AMBIENT_LIGHT, 0.5);
exports.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(exports.CONSTANTS.DIRECTIONAL_LIGHT, 1);
directionalLight.position.set(50, 100, 50);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.shadow.camera.left = -100;
directionalLight.shadow.camera.right = 100;
directionalLight.shadow.camera.top = 100;
directionalLight.shadow.camera.bottom = -100;
exports.scene.add(directionalLight);
// Hemisphere light for sky
const hemisphereLight = new THREE.HemisphereLight(0xff6644, 0x080820, 0.6);
exports.scene.add(hemisphereLight);
// Generate initial chunks
for (let i = 0; i < 5; i++) {
const chunk = generateChunk(-i * exports.CONSTANTS.CHUNK_SIZE, 1);
exports.CHUNKS.push(chunk);
}
// Collect all obstacles and orbs
updateEnvironmentArrays();
}
function updateEnvironmentArrays() {
exports.obstacles = [];
exports.orbs = [];
for (const chunk of exports.CHUNKS) {
exports.obstacles.push(...chunk.obstacles);
exports.orbs.push(...chunk.orbs);
}
// Remove old chunks
const minZ = exports.STATE.position.z - 400;
for (let i = exports.CHUNKS.length - 1; i >= 0; i--) {
if (exports.CHUNKS[i].position < minZ) {
// Remove meshes from scene
for (const obstacle of exports.CHUNKS[i].obstacles) {
exports.scene.remove(obstacle.mesh);
obstacle.mesh.geometry.dispose();
obstacle.mesh.material.dispose();
}
for (const orb of exports.CHUNKS[i].orbs) {
exports.scene.remove(orb.mesh);
orb.mesh.geometry.dispose();
orb.mesh.material.dispose();
}
exports.CHUNKS.splice(i, 1);
}
}
}
function generateAhead() {
const lastChunk = exports.CHUNKS[exports.CHUNKS.length - 1];
const nextPosition = lastChunk.position - exports.CONSTANTS.CHUNK_SIZE;
if (nextPosition > exports.STATE.position.z - exports.CONSTANTS.CHUNK_GENERATION_DISTANCE * exports.CONSTANTS.CHUNK_SIZE) {
const difficulty = 1 + exports.STATE.score / 1000;
const chunk = generateChunk(nextPosition, difficulty);
exports.CHUNKS.push(chunk);
}
}
function cleanupBehind() {
const minZ = exports.STATE.position.z - exports.CONSTANTS.CHUNK_SIZE * 2;
const chunksToRemove = [];
for (let i = exports.CHUNKS.length - 1; i >= 0; i--) {
if (exports.CHUNKS[i].position < minZ) {
chunksToRemove.push(exports.CHUNKS[i]);
}
}
for (const chunk of chunksToRemove) {
for (const obstacle of chunk.obstacles) {
exports.scene.remove(obstacle.mesh);
}
for (const orb of chunk.orbs) {
exports.scene.remove(orb.mesh);
}
}
exports.CHUNKS = exports.CHUNKS.filter((chunk) => chunk.position >= minZ);
}
function updateEnvironment() {
generateAhead();
cleanupBehind();
updateEnvironmentArrays();
}
// Player Movement
function updatePlayer(dt) {
const speed = exports.STATE.isBoosting ? exports.GAME_STATE.speed * exports.CONSTANTS.BOOST_MULTIPLIER : exports.GAME_STATE.speed;
// Input handling
const moveX = (exports.INPUT.left ? 1 : 0) - (exports.INPUT.right ? 1 : 0);
const moveY = (exports.INPUT.down ? 1 : 0) - (exports.INPUT.up ? 1 : 0);
// Acceleration
exports.STATE.velocity.x += moveX * exports.CONSTANTS.ACCELERATION * dt;
exports.STATE.velocity.y += moveY * exports.CONSTANTS.ACCELERATION * dt;
// Limit speed
exports.STATE.velocity.clampLength(0, speed);
// Apply velocity
exports.STATE.position.add(exports.STATE.velocity.clone().multiplyScalar(dt));
// Banking based on horizontal velocity
const bankAngle = THREE.MathUtils.degToRad(30);
const targetBank = -exports.STATE.velocity.x * 0.3;
exports.STATE.rotation.z = THREE.MathUtils.lerp(exports.STATE.rotation.z, targetBank, dt * 5);
// Pitch based on vertical velocity
const pitchAngle = THREE.MathUtils.degToRad(20);
const targetPitch = -exports.STATE.velocity.y * 0.2;
exports.STATE.rotation.x = THREE.MathUtils.lerp(exports.STATE.rotation.x, targetPitch, dt * 5);
// Energy decay
exports.STATE.energy -= exports.CONSTANTS.ENERGY_DECAY * dt;
if (exports.STATE.energy <= 0) {
gameOver();
return;
}
// Boost management
if (exports.STATE.turboMeter >= 100) {
exports.STATE.turboMeter = 0;
exports.STATE.isBoosting = true;
setTimeout(() => {
exports.STATE.isBoosting = false;
}, 3000);
}
// Trail
addTrailPoint(exports.STATE.position.clone());
updateTrail();
// Update game state
exports.GAME_STATE.speed = Math.max(speed, exports.CONSTANTS.PLAYER_SPEED);
exports.GAME_STATE.distance += speed * dt;
exports.GAME_STATE.difficultyMultiplier = 1 + exports.STATE.score / 1000;
// Camera follow
exports.camera.position.x = THREE.MathUtils.lerp(exports.camera.position.x, exports.STATE.position.x * 0.3, dt * 2);
exports.camera.position.y = THREE.MathUtils.lerp(exports.camera.position.y, exports.STATE.position.y + 15, dt * 2);
exports.camera.position.z = exports.STATE.position.z + 30;
// FOV change on boost
const targetFOV = exports.STATE.isBoosting ? 90 : 75;
exports.camera.fov = THREE.MathUtils.lerp(exports.camera.fov, targetFOV, dt * 2);
exports.camera.updateProjectionMatrix();
// Camera shake
updateCameraShake();
// Audio update
exports.audioManager.updateEngineSpeed(speed, exports.CONSTANTS.MAX_SPEED);
}
// HUD Updates
function updateHUD() {
// Speed
const speedPercent = (exports.STATE.velocity.length() / exports.STATE.maxSpeed) * 100;
document.getElementById('speed-fill').style.width = `${speedPercent}%`;
document.getElementById('speed-value').textContent = Math.round(exports.STATE.velocity.length()).toString();
// Multiplier
const multiplierEl = document.getElementById('multiplier-value');
multiplierEl.textContent = `x${exports.STATE.multiplier}`;
if (exports.STATE.multiplier > 1) {
multiplierEl.classList.add('pulse');
}
else {
multiplierEl.classList.remove('pulse');
}
// Energy
const energyPercent = (exports.STATE.energy / exports.STATE.maxEnergy) * 100;
document.getElementById('energy-fill').style.width = `${energyPercent}%`;
// Turbo
if (exports.STATE.turboMeter >= 100) {
document.getElementById('energy-fill').style.background = 'linear-gradient(90deg, #00ff88, #00cc66)';
document.getElementById('energy-fill').style.boxShadow = '0 0 10px #00ff88';
}
else {
document.getElementById('energy-fill').style.background = 'linear-gradient(90deg, #ff6b35, #ffcc00)';
document.getElementById('energy-fill').style.boxShadow = 'none';
}
}
// Game Over
function gameOver() {
exports.STATE.isPlaying = false;
exports.audioManager.playCrash();
document.getElementById('game-over-screen').classList.remove('hidden');
document.getElementById('final-score').textContent = Math.round(exports.STATE.score).toString();
document.getElementById('final-speed').textContent = Math.round(exports.STATE.velocity.length()).toString();
document.getElementById('final-misses').textContent = exports.STATE.nearMisses.toString();
}
// Start Game
function startGame() {
exports.audioManager.resume();
exports.STATE.isPlaying = true;
exports.STATE.score = 0;
exports.STATE.energy = 100;
exports.STATE.multiplier = 1;
exports.STATE.nearMisses = 0;
exports.STATE.turboMeter = 0;
exports.STATE.position.set(0, 0, 0);
exports.STATE.velocity.set(0, 0, exports.CONSTANTS.PLAYER_SPEED);
exports.STATE.rotation.set(0, 0, 0);
exports.GAME_STATE.difficultyMultiplier = 1;
// Clear obstacles
for (const chunk of exports.CHUNKS) {
for (const obstacle of chunk.obstacles) {
exports.scene.remove(obstacle.mesh);
}
for (const orb of chunk.orbs) {
exports.scene.remove(orb.mesh);
}
}
// Clear orbs array
exports.orbs.forEach((orb) => {
exports.scene.remove(orb.mesh);
});
exports.orbs = [];
// Generate initial chunks again
exports.CHUNKS.length = 0;
for (let i = 0; i < 5; i++) {
const chunk = generateChunk(-i * exports.CONSTANTS.CHUNK_SIZE, 1);
exports.CHUNKS.push(chunk);
}
updateEnvironmentArrays();
// Hide screens
document.getElementById('start-screen').classList.add('hidden');
document.getElementById('game-over-screen').classList.add('hidden');
}
// Main Game Loop
function gameLoop() {
if (!exports.STATE.isPlaying)
return;
const dt = Math.min(1 / 60, 0.016);
updatePlayer(dt);
updateEnvironment();
checkCollisions();
updateHUD();
exports.composer.render();
requestAnimationFrame(gameLoop);
}
// Input Handling
function setupInputs() {
document.addEventListener('keydown', (e) => {
switch (e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
exports.INPUT.left = true;
break;
case 'ArrowRight':
case 'd':
case 'D':
exports.INPUT.right = true;
break;
case 'ArrowUp':
case 'w':
case 'W':
exports.INPUT.up = true;
break;
case 'ArrowDown':
case 's':
case 'S':
exports.INPUT.down = true;
break;
case 'Shift':
if (exports.STATE.turboMeter >= 100) {
exports.STATE.isBoosting = true;
setTimeout(() => {
exports.STATE.isBoosting = false;
}, 3000);
}
break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
exports.INPUT.left = false;
break;
case 'ArrowRight':
case 'd':
case 'D':
exports.INPUT.right = false;
break;
case 'ArrowUp':
case 'w':
case 'W':
exports.INPUT.up = false;
break;
case 'ArrowDown':
case 's':
case 'S':
exports.INPUT.down = false;
break;
case 'Shift':
exports.STATE.isBoosting = false;
break;
}
});
document.getElementById('start-btn').addEventListener('click', startGame);
document.getElementById('restart-btn').addEventListener('click', startGame);
}
// Window Resize
function handleResize() {
exports.camera.aspect = window.innerWidth / window.innerHeight;
exports.camera.updateProjectionMatrix();
exports.renderer.setSize(window.innerWidth, window.innerHeight);
exports.composer.setSize(window.innerWidth, window.innerHeight);
}
// Initialize
async function init() {
exports.audioManager = new AudioContext();
initEnvironment();
initTrailSystem();
const playerCraft = createPlayerCraft();
exports.scene.add(playerCraft);
setupInputs();
window.addEventListener('resize', handleResize);
requestAnimationFrame(gameLoop);
}
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>