Canyon Run
Qwen 3.5 122b a10b · typescript

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 63,306 bytes · SHA-256 bb6066a6fb25
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>High-Speed Canyon Racer</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ── css: layout ── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #0a0a1a;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
}
#game-canvas {
display: block;
width: 100%;
height: 100%;
}
/* ── css: hud ── */
#hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
/* Speedometer - bottom left */
#speedometer {
position: absolute;
bottom: 30px;
left: 30px;
display: flex;
align-items: center;
gap: 15px;
}
#speed-bar {
width: 200px;
height: 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
overflow: hidden;
}
#speed-bar::after {
content: '';
display: block;
width: var(--speed-pct, 0%);
height: 100%;
background: linear-gradient(90deg, #ff6b35, #f7c59f);
transition: width 0.1s ease-out;
}
#speed-text {
color: #f7c59f;
font-size: 24px;
font-weight: bold;
text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}
/* Multiplier display - top center */
#multiplier-display {
position: absolute;
top: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
#mult-value {
display: block;
font-size: 64px;
font-weight: bold;
color: #ffd700;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.8), 0 0 40px rgba(255, 215, 0, 0.4);
transition: transform 0.15s ease-out;
}
#mult-value.pulse {
animation: pulse-glow 0.3s ease-out;
}
@keyframes pulse-glow {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.mult-label {
display: block;
font-size: 12px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 3px;
margin-top: 5px;
}
/* Energy bar - bottom center */
#energy-container {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
#energy-bar {
width: 300px;
height: 12px;
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
overflow: hidden;
}
#energy-fill {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #00d4ff, #00ff88);
transition: width 0.1s ease-out;
box-shadow: 0 0 15px rgba(0, 212, 255, 0.6);
}
.energy-label {
display: block;
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 2px;
margin-top: 5px;
}
/* Boost meter - bottom right */
#boost-meter {
position: absolute;
bottom: 30px;
right: 30px;
text-align: right;
}
#boost-fill {
width: 0%;
height: 15px;
background: linear-gradient(90deg, #ff0066, #ff6b35);
border-radius: 3px;
transition: width 0.1s ease-out;
box-shadow: 0 0 20px rgba(255, 0, 102, 0.6);
margin-bottom: 5px;
}
.boost-label {
display: block;
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 2px;
}
/* Score display - top right */
#score-display {
position: absolute;
top: 30px;
right: 30px;
text-align: right;
}
#score-value {
display: block;
font-size: 36px;
font-weight: bold;
color: #f7c59f;
text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}
.score-label {
display: block;
font-size: 10px;
color: rgba(255, 255, 255, 0.6);
letter-spacing: 2px;
}
/* ── css: screens ── */
.hidden {
display: none !important;
}
/* Start screen */
#start-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(10, 10, 26, 0.95) 0%, rgba(40, 20, 40, 0.95) 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
pointer-events: auto;
}
#start-screen h1 {
font-size: 72px;
color: #ffd700;
text-shadow: 0 0 30px rgba(255, 215, 0, 0.8), 0 0 60px rgba(255, 107, 53, 0.5);
letter-spacing: 10px;
margin-bottom: 10px;
}
#start-screen .subtitle {
font-size: 20px;
color: rgba(247, 197, 159, 0.8);
letter-spacing: 5px;
margin-bottom: 50px;
}
.controls-info {
background: rgba(0, 0, 0, 0.3);
padding: 30px 50px;
border-radius: 10px;
margin-bottom: 40px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.controls-info p {
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
margin: 10px 0;
line-height: 1.8;
}
.controls-info strong {
color: #f7c59f;
}
#start-btn, #restart-btn {
background: linear-gradient(135deg, #ff6b35 0%, #ff0066 100%);
border: none;
color: white;
font-size: 24px;
font-weight: bold;
letter-spacing: 5px;
padding: 20px 60px;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 30px rgba(255, 107, 53, 0.4);
}
#start-btn:hover, #restart-btn:hover {
transform: translateY(-3px);
box-shadow: 0 10px 40px rgba(255, 107, 53, 0.6);
}
#start-btn:active, #restart-btn:active {
transform: translateY(-1px);
}
/* Game over screen */
#game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, rgba(10, 10, 26, 0.98) 0%, rgba(40, 10, 20, 0.98) 100%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
pointer-events: auto;
}
#game-over h1 {
font-size: 64px;
color: #ff0066;
text-shadow: 0 0 30px rgba(255, 0, 102, 0.8), 0 0 60px rgba(255, 0, 102, 0.4);
letter-spacing: 15px;
margin-bottom: 40px;
}
#final-score, #max-speed-display {
font-size: 28px;
color: #f7c59f;
margin: 10px 0;
text-shadow: 0 0 10px rgba(247, 197, 159, 0.5);
}
#final-score {
font-size: 36px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="game-canvas"></canvas>
<div id="hud">
<div id="speedometer">
<div id="speed-bar"></div>
<span id="speed-text">0 km/h</span>
</div>
<div id="multiplier-display">
<span id="mult-value">1.0x</span>
<span class="mult-label">MULTIPLIER</span>
</div>
<div id="energy-container">
<div id="energy-bar">
<div id="energy-fill"></div>
</div>
<span class="energy-label">ENERGY</span>
</div>
<div id="boost-meter">
<div id="boost-fill"></div>
<span class="boost-label">BOOST READY</span>
</div>
<div id="score-display">
<span id="score-value">0</span>
<span class="score-label">SCORE</span>
</div>
</div>
<div id="game-over" class="hidden">
<h1>CRASHED!</h1>
<div id="final-score">Score: 0</div>
<div id="max-speed-display">Max Speed: 0 km/h</div>
<button id="restart-btn">RESTART</button>
</div>
<div id="start-screen">
<h1>CANYON RACER</h1>
<p class="subtitle">High-Speed Procedural Adventure</p>
<div class="controls-info">
<p><strong>Arrow Keys / WASD</strong> - Steer & Pitch</p>
<p><strong>Space</strong> - Boost (when meter full)</p>
<p>Collect orbs for energy • Pass close to walls for multiplier</p>
</div>
<button id="start-btn">START RUN</button>
</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./player":"src/player.ts","./world":"src/world.ts","./particles":"src/particles.ts","./audio":"src/audio.ts","./hud":"src/hud.ts","./state":"src/state.ts"},"src/player.ts":{"./state":"src/state.ts"},"src/world.ts":{"./state":"src/state.ts"},"src/particles.ts":{"./state":"src/state.ts"},"src/hud.ts":{"./state":"src/state.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 });
exports.game = void 0;
const player_1 = require("./player");
const world_1 = require("./world");
const particles_1 = require("./particles");
const audio_1 = require("./audio");
const hud_1 = require("./hud");
const state_1 = require("./state");
// Three.js globals (declared in globals.d.ts)
class Game {
constructor() {
this.scene = null;
this.camera = null;
this.renderer = null;
this.directionalLight = null;
this.ambientLight = null;
this.fogColor = 0x1a0f2e;
this.lastTime = 0;
this.animationId = null;
this.cameraShake = 0;
this.baseFOV = 75;
this.initThree();
this.setupLights();
this.setupInputs();
this.setupEventListeners();
}
initThree() {
const canvas = document.getElementById('game-canvas');
// Scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(this.fogColor);
const fogObj = { color: new THREE.Color(this.fogColor), near: 50, far: 300 };
this.scene.fog = fogObj;
// Camera
this.camera = new THREE.PerspectiveCamera(this.baseFOV, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(0, 8, 15);
// Renderer
this.renderer = new THREE.WebGLRenderer({
canvas,
antialias: true,
powerPreference: 'high-performance'
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap || 2;
// Handle resize
window.addEventListener('resize', () => this.onResize());
}
setupLights() {
if (!this.scene)
return;
// Ambient light for base illumination
this.ambientLight = new THREE.AmbientLight(0xffd4a3, 0.4);
if (this.ambientLight && this.scene)
this.scene.add(this.ambientLight);
// Directional light (sun) - golden hour angle
this.directionalLight = new THREE.DirectionalLight(0xff9933, 1.2);
this.directionalLight.position.set(50, 30, -50);
this.directionalLight.castShadow = true;
this.directionalLight.shadow.mapSize.x = 2048;
this.directionalLight.shadow.mapSize.y = 2048;
const shadowCam = this.directionalLight.shadow.camera;
shadowCam.near = 10;
shadowCam.far = 200;
shadowCam.left = -50;
shadowCam.right = 50;
shadowCam.top = 50;
shadowCam.bottom = -50;
this.scene.add(this.directionalLight);
// Add a point light near player for engine glow effect
const playerLight = new THREE.PointLight(0x00ffff, 0.5, 20);
playerLight.position.set(0, 5, 0);
this.scene.add(playerLight);
}
setupInputs() {
document.addEventListener('keydown', (e) => this.onKeyDown(e));
document.addEventListener('keyup', (e) => this.onKeyUp(e));
}
setupEventListeners() {
window.addEventListener('gameStart', () => this.startGame());
window.addEventListener('gameRestart', () => this.restartGame());
}
onKeyDown(e) {
const state = (0, state_1.getState)();
if (state.isGameOver)
return;
switch (e.code) {
case 'ArrowLeft':
case 'KeyA':
case 'ArrowRight':
case 'KeyD':
case 'ArrowUp':
case 'KeyW':
case 'ArrowDown':
case 'KeyS':
e.preventDefault();
player_1.player.setInput(e.code, true);
break;
case 'Space':
e.preventDefault();
player_1.player.setInput('Space', true);
if ((0, state_1.canBoost)()) {
this.activateBoost();
}
break;
}
}
onKeyUp(e) {
player_1.player.setInput(e.code, false);
}
activateBoost() {
(0, state_1.consumeBoost)();
player_1.player.isBoosting = true;
player_1.player.boostTimer = 1.5;
// FOV increase for speed sensation
if (this.camera) {
this.camera.fov = this.baseFOV + 30;
this.camera.updateProjectionMatrix();
}
// Camera shake
this.cameraShake = 0.3;
// Audio feedback
audio_1.audio.playBoost();
// Boost sound continues during boost
}
onResize() {
if (!this.camera || !this.renderer)
return;
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
startGame() {
(0, state_1.resetGame)();
(0, state_1.setState)({
isRunning: true,
isGameOver: false,
score: 0,
multiplier: 1.0,
energy: 100,
boostAmount: 0,
speed: 50,
maxSpeed: 50,
distance: 0,
time: 0
});
player_1.player.reset();
world_1.world.cleanup();
hud_1.hud.hideStartScreen();
hud_1.hud.hideGameOver();
// Initialize audio
audio_1.audio.init();
audio_1.audio.setEngineVolume(1);
// Start game loop
if (!this.animationId) {
this.lastTime = performance.now();
this.animate();
}
}
restartGame() {
this.startGame();
}
gameOver() {
const state = (0, state_1.getState)();
(0, state_1.setState)({
isRunning: false,
isGameOver: true
});
audio_1.audio.setEngineVolume(0);
audio_1.audio.playCrash();
hud_1.hud.showGameOver(state.score, state.maxSpeed);
}
animate() {
const currentTime = performance.now();
const deltaTime = Math.min((currentTime - this.lastTime) / 1000, 0.1);
this.lastTime = currentTime;
const state = (0, state_1.getState)();
if (state.isRunning && !state.isGameOver) {
this.update(deltaTime);
}
this.render();
this.animationId = requestAnimationFrame(() => this.animate());
}
update(deltaTime) {
const state = (0, state_1.getState)();
// Update player
player_1.player.update(deltaTime);
// Update world generation based on player position
world_1.world.update(player_1.player.physics.position.z);
// Energy decay
const energyDecay = 2 * deltaTime; // 2% per second
(0, state_1.setEnergy)(state.energy - energyDecay);
if (state.energy <= 0) {
this.gameOver();
return;
}
// Collision detection
const collisionResult = world_1.world.checkCollision(player_1.player.physics.position, 1.5);
if (collisionResult.collided) {
this.gameOver();
return;
}
// Check near-miss for multiplier
if (collisionResult.distance < 5 && collisionResult.distance > 1.5) {
const proximityBonus = (5 - collisionResult.distance) / 3.5;
const newMultiplier = Math.min(5, (0, state_1.getMultiplier)() + proximityBonus * 0.5);
(0, state_1.updateMultiplier)(newMultiplier);
if (proximityBonus > 0.5) {
hud_1.hud.pulseMultiplier();
(0, state_1.triggerMultiplierPulse)();
// Near miss bonus points
(0, state_1.addScore)(50 * proximityBonus);
// Audio feedback for near miss
audio_1.audio.playNearMiss((0, state_1.getMultiplier)());
// Subtle camera shake
this.cameraShake += proximityBonus * 0.1;
}
}
// Check orb collection
const collectedOrb = world_1.world.checkOrbCollection(player_1.player.physics.position);
if (collectedOrb) {
// Energy recharge
(0, state_1.setEnergy)(state.energy + 25);
// Score bonus
(0, state_1.addScore)(100 * (0, state_1.getMultiplier)());
// Boost charge
(0, state_1.modifyBoost)(20);
// Audio feedback
audio_1.audio.playCollectible();
// Visual feedback
this.cameraShake += 0.1;
}
// Passive boost charging at high speeds
if (state.speed > 100) {
(0, state_1.modifyBoost)(deltaTime * 5);
}
// Update score based on distance
const distanceTraveled = -player_1.player.physics.position.z;
const speedBonus = state.speed / 100;
(0, state_1.addScore)(deltaTime * 10 * speedBonus * (0, state_1.getMultiplier)());
// Update speed in state
state.speed = Math.floor(player_1.player.physics.speed * (player_1.player.isBoosting ? 2 : 1));
if (state.speed > state.maxSpeed) {
state.maxSpeed = state.speed;
}
// Gradually increase speed over time
if (!player_1.player.isBoosting && state.speed < 150) {
player_1.player.physics.speed += deltaTime * 2;
}
// Reduce boost when active
if (player_1.player.isBoosting) {
player_1.player.boostTimer -= deltaTime;
if (player_1.player.boostTimer <= 0) {
player_1.player.isBoosting = false;
// Restore FOV
if (this.camera) {
this.camera.fov = this.baseFOV;
this.camera.updateProjectionMatrix();
}
}
}
// Update camera to follow player with smooth lag
if (this.camera) {
const targetX = player_1.player.physics.position.x * 0.3;
const targetY = player_1.player.physics.position.y + 6;
const targetZ = player_1.player.physics.position.z + 15;
this.camera.position.x += (targetX - this.camera.position.x) * deltaTime * 2;
this.camera.position.y += (targetY - this.camera.position.y) * deltaTime * 2;
this.camera.position.z += (targetZ - this.camera.position.z) * deltaTime * 3;
// Camera looks slightly ahead of player
const lookTarget = new THREE.Vector3(player_1.player.physics.position.x, player_1.player.physics.position.y, player_1.player.physics.position.z - 20);
this.camera.lookAt(lookTarget);
// Apply camera shake
if (this.cameraShake > 0) {
this.camera.position.x += (Math.random() - 0.5) * this.cameraShake;
this.camera.position.y += (Math.random() - 0.5) * this.cameraShake;
this.cameraShake *= 0.95;
if (this.cameraShake < 0.01)
this.cameraShake = 0;
}
}
// Update particle systems
particles_1.particles.update(deltaTime, player_1.player.physics.position);
// Update audio
audio_1.audio.setSpeed(state.speed);
// Update HUD
hud_1.hud.update();
}
render() {
if (!this.scene || !this.camera || !this.renderer)
return;
// Update player mesh position in scene
const playerMesh = player_1.player.getMesh();
if (playerMesh) {
// Remove old player mesh if exists
const existingPlayer = this.scene.children.find(c => c === playerMesh);
if (!existingPlayer) {
this.scene.add(playerMesh);
}
}
// Add trail particles
const trail = player_1.player.getTrail();
if (trail && !this.scene.children.includes(trail)) {
this.scene.add(trail);
}
// Add world meshes (only new ones to avoid duplicates)
const worldMeshes = world_1.world.getAllMeshes();
for (const mesh of worldMeshes) {
if (!this.scene.children.includes(mesh)) {
this.scene.add(mesh);
}
}
// Add particle systems
const dust = particles_1.particles.getDustPoints();
const debris = particles_1.particles.getDebrisPoints();
const sparks = particles_1.particles.getSparkPoints();
if (dust && !this.scene.children.includes(dust)) {
this.scene.add(dust);
}
if (debris && !this.scene.children.includes(debris)) {
this.scene.add(debris);
}
if (sparks && !this.scene.children.includes(sparks)) {
this.scene.add(sparks);
}
// Render
this.renderer.render(this.scene, this.camera);
}
}
// Create and start the game
const game = new Game();
exports.game = game;
// Show start screen initially
hud_1.hud.showStartScreen();
};
// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};
// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
// Game state management
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultState = void 0;
exports.getState = getState;
exports.setState = setState;
exports.resetGame = resetGame;
exports.addScore = addScore;
exports.updateMultiplier = updateMultiplier;
exports.setEnergy = setEnergy;
exports.modifyBoost = modifyBoost;
exports.canBoost = canBoost;
exports.consumeBoost = consumeBoost;
exports.getMultiplier = getMultiplier;
exports.triggerMultiplierPulse = triggerMultiplierPulse;
exports.defaultState = {
isRunning: false,
isGameOver: false,
score: 0,
multiplier: 1.0,
energy: 100,
boostAmount: 0,
speed: 0,
maxSpeed: 0,
distance: 0,
time: 0
};
let currentState = { ...exports.defaultState };
function getState() {
return { ...currentState };
}
function setState(newState) {
currentState = { ...currentState, ...newState };
}
function resetGame() {
currentState = { ...exports.defaultState };
currentState.speed = 50; // Starting speed
currentState.maxSpeed = 50;
}
function addScore(points) {
currentState.score += points * currentState.multiplier;
}
function updateMultiplier(value) {
currentState.multiplier = Math.max(1, Math.min(5, value));
}
function setEnergy(value) {
currentState.energy = Math.max(0, Math.min(100, value));
}
function modifyBoost(amount) {
currentState.boostAmount = Math.max(0, Math.min(100, currentState.boostAmount + amount));
}
function canBoost() {
return currentState.boostAmount >= 100 && currentState.isRunning && !currentState.isGameOver;
}
function consumeBoost() {
if (canBoost()) {
currentState.boostAmount = 0;
}
}
function getMultiplier() {
return currentState.multiplier;
}
function triggerMultiplierPulse() {
// Signal for UI pulse effect
currentState.time += 0.1;
}
};
// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.player = exports.Player = void 0;
const state_1 = require("./state");
class Player {
constructor() {
this.mesh = null;
this.engineGlow = null;
this.trailParticles = null;
this.isBoosting = false;
this.boostTimer = 0;
this.targetBanking = 0;
this.inputState = {
left: false,
right: false,
up: false,
down: false,
boost: false
};
this.particles = [];
this.particleIndex = 0;
this.physics = {
position: new THREE.Vector3(0, 5, 0),
velocity: new THREE.Vector3(0, 0, 0),
rotation: new THREE.Euler(0, 0, 0),
speed: 50,
maxSpeed: 150,
turnSpeed: 3,
pitchSpeed: 2,
bankingAngle: 0,
targetBanking: 0
};
this.createCraft();
this.createTrailSystem();
}
createCraft() {
const group = new THREE.Group();
// Main fuselage - sleek geometric shape (pointing down Z)
const fuselageGeo = new THREE.ConeGeometry(0.3, 2, 8);
const fuselageMat = new THREE.MeshPhongMaterial({
color: 0xc0c0c0,
shininess: 100,
flatShading: true
});
const fuselage = new THREE.Mesh(fuselageGeo, fuselageMat);
fuselage.rotation.x = Math.PI / 2;
group.add(fuselage);
// Wings
const wingGeo = new THREE.BoxGeometry(2.5, 0.05, 0.8);
const wingMat = new THREE.MeshPhongMaterial({
color: 0xa0a0a0,
shininess: 80,
flatShading: true
});
const wings = new THREE.Mesh(wingGeo, wingMat);
wings.position.z = 0.3;
group.add(wings);
// Tail fins
const tailGeo = new THREE.BoxGeometry(0.8, 0.4, 0.05);
const tail = new THREE.Mesh(tailGeo, wingMat);
tail.position.set(0, 0.2, 0.8);
group.add(tail);
const tailVertGeo = new THREE.BoxGeometry(0.05, 0.5, 0.6);
const tailVert = new THREE.Mesh(tailVertGeo, wingMat);
tailVert.position.set(0, 0.25, 0.8);
group.add(tailVert);
// Engine glow
const engineGeo = new THREE.SphereGeometry(0.2, 8, 8);
const engineMat = new THREE.MeshBasicMaterial({
color: 0x00ffff,
transparent: true,
opacity: 0.8
});
this.engineGlow = new THREE.Mesh(engineGeo, engineMat);
this.engineGlow.position.z = 1.1;
group.add(this.engineGlow);
group.castShadow = true;
this.mesh = group;
}
createTrailSystem() {
// Create multiple particle buffers for continuous trail
const particleCount = 50;
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const material = new THREE.PointsMaterial({
color: 0x00ffff,
size: 0.3,
transparent: true,
opacity: 0.6,
blending: THREE.AdditiveBlending || 1
});
this.trailParticles = new THREE.Points(geometry, material);
this.trailParticles.visible = false;
}
update(deltaTime) {
const state = (0, state_1.getState)();
// Handle boost
if (this.inputState.boost && (0, state_1.canBoost)()) {
this.isBoosting = true;
(0, state_1.consumeBoost)();
this.boostTimer = 1.5; // Boost duration in seconds
}
if (this.isBoosting) {
this.boostTimer -= deltaTime;
if (this.boostTimer <= 0) {
this.isBoosting = false;
}
}
// Calculate effective speed
const baseSpeed = this.physics.speed;
const currentSpeed = this.isBoosting ? baseSpeed * 2 : baseSpeed;
// Update input-based movement
const moveLeft = this.inputState.left ? 1 : 0;
const moveRight = this.inputState.right ? 1 : 0;
const moveUp = this.inputState.up ? 1 : 0;
const moveDown = this.inputState.down ? 1 : 0;
// Banking calculation
const turnInput = moveRight - moveLeft;
this.targetBanking = turnInput * 0.5; // Max 30 degrees
this.physics.bankingAngle += (this.targetBanking - this.physics.bankingAngle) * deltaTime * 5;
// Apply rotation based on banking
this.physics.rotation.z = this.physics.bankingAngle;
this.physics.rotation.x = (moveUp - moveDown) * 0.3;
this.physics.rotation.y = -turnInput * 0.2;
// Position updates
this.physics.position.x += turnInput * this.physics.turnSpeed * deltaTime * 10;
this.physics.position.y += (moveUp - moveDown) * this.physics.pitchSpeed * deltaTime * 10;
// Forward movement (along negative Z)
this.physics.position.z -= currentSpeed * deltaTime;
// Bounds checking
this.physics.position.x = Math.max(-25, Math.min(25, this.physics.position.x));
this.physics.position.y = Math.max(2, Math.min(15, this.physics.position.y));
// Update mesh if exists
if (this.mesh) {
this.mesh.position.copy(this.physics.position);
this.mesh.rotation.copy(this.physics.rotation);
// Engine glow pulsing
if (this.engineGlow) {
const pulse = 1 + Math.sin(Date.now() * 0.01) * 0.2;
this.engineGlow.scale.set(pulse, pulse, pulse);
this.engineGlow.material.opacity = 0.6 + Math.random() * 0.4;
}
}
// Update trail particles
this.updateTrail(deltaTime);
// Update state
state.speed = Math.floor(currentSpeed);
if (state.speed > state.maxSpeed) {
state.maxSpeed = state.speed;
}
}
updateTrail(deltaTime) {
if (!this.trailParticles || !this.mesh)
return;
const positions = this.trailParticles.geometry.attributes.position.array;
// Shift all particles back
for (let i = (this.particleIndex + 1) % 50; i !== this.particleIndex; i = (i + 1) % 50) {
const idx = i * 3;
positions[idx] = this.mesh.position.x + (Math.random() - 0.5) * 0.3;
positions[idx + 1] = this.mesh.position.y + (Math.random() - 0.5) * 0.2;
positions[idx + 2] = this.mesh.position.z + 1 + (50 - i) * 0.2;
}
this.trailParticles.geometry.attributes.position.needsUpdate = true;
this.trailParticles.visible = true;
this.particleIndex = (this.particleIndex + 1) % 50;
}
setInput(key, pressed) {
switch (key) {
case 'ArrowLeft':
case 'KeyA':
this.inputState.left = pressed;
break;
case 'ArrowRight':
case 'KeyD':
this.inputState.right = pressed;
break;
case 'ArrowUp':
case 'KeyW':
this.inputState.up = pressed;
break;
case 'ArrowDown':
case 'KeyS':
this.inputState.down = pressed;
break;
case 'Space':
this.inputState.boost = pressed;
break;
}
}
getMesh() {
return this.mesh;
}
getTrail() {
return this.trailParticles;
}
reset() {
this.physics.position.set(0, 5, 0);
this.physics.velocity.set(0, 0, 0);
this.physics.rotation.set(0, 0, 0);
this.physics.bankingAngle = 0;
this.isBoosting = false;
this.boostTimer = 0;
}
}
exports.Player = Player;
exports.player = new Player();
};
// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.world = exports.World = void 0;
const state_1 = require("./state");
class World {
constructor() {
this.chunks = [];
this.chunkLength = 50;
this.chunkCount = 8;
this.currentZ = 0;
this.canyonWidth = 60;
this.wallHeight = 20;
// Colors for golden hour aesthetic
this.colors = {
wall: 0x8B4513,
wallDark: 0x5D2906,
monolith: 0x4a4a4a,
orb: 0x00ffff,
ground: 0x2d1f1f
};
this.generateInitialChunks();
}
generateInitialChunks() {
for (let i = 0; i < this.chunkCount; i++) {
this.generateChunk();
}
}
generateChunk() {
const chunk = {
meshes: [],
obstacles: [],
zPosition: this.currentZ - this.chunkLength * (this.chunks.length + 1)
};
const z = chunk.zPosition;
// Generate canyon walls
this.createWalls(chunk, z);
// Generate obstacles based on difficulty
const state = (0, state_1.getState)();
const difficulty = Math.min(1, state.score / 10000);
this.generateObstacles(chunk, z, difficulty);
this.chunks.push(chunk);
}
createWalls(chunk, z) {
const wallThickness = 5;
const wallSegments = 10;
const segmentLength = this.chunkLength / wallSegments;
// Left and right walls with procedural variation
for (let i = 0; i < wallSegments; i++) {
const segmentZ = z - i * segmentLength;
// Add some procedural variation to wall height and position
const variation = Math.sin(segmentZ * 0.1) * 5;
const wallH = this.wallHeight + variation;
// Left wall
const leftWallGeo = new THREE.BoxGeometry(wallThickness, wallH, segmentLength + 2);
const wallMat = new THREE.MeshPhongMaterial({
color: i % 2 === 0 ? this.colors.wall : this.colors.wallDark,
flatShading: true
});
const leftWall = new THREE.Mesh(leftWallGeo, wallMat);
leftWall.position.set(-this.canyonWidth / 2, wallH / 2, segmentZ);
leftWall.castShadow = true;
leftWall.receiveShadow = true;
chunk.meshes.push(leftWall);
// Right wall
const rightWall = new THREE.Mesh((leftWall.geometry), (leftWall.material));
rightWall.position.x = this.canyonWidth / 2;
rightWall.rotation.copy(leftWall.rotation);
rightWall.position.x = this.canyonWidth / 2;
chunk.meshes.push(rightWall);
}
// Ground plane
const groundGeo = new THREE.PlaneGeometry(this.canyonWidth + 20, this.chunkLength + 10);
const groundMat = new THREE.MeshPhongMaterial({
color: this.colors.ground,
flatShading: true
});
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.position.z = z - this.chunkLength / 2;
ground.receiveShadow = true;
chunk.meshes.push(ground);
}
generateObstacles(chunk, z, difficulty) {
// Number of obstacles increases with difficulty
const obstacleCount = 2 + Math.floor(difficulty * 5);
for (let i = 0; i < obstacleCount; i++) {
const obstacleType = this.randomObstacleType(difficulty);
const xPos = (Math.random() - 0.5) * (this.canyonWidth - 10);
const yPos = 2 + Math.random() * 8;
const zPos = z - (Math.random() * this.chunkLength);
switch (obstacleType) {
case 'monolith':
this.createMonolith(chunk, xPos, yPos, zPos);
break;
case 'squeeze':
this.createSqueeze(chunk, xPos, yPos, zPos);
break;
case 'orb':
this.createOrb(chunk, xPos, yPos, zPos);
break;
}
}
}
randomObstacleType(difficulty) {
const rand = Math.random();
if (rand < 0.3)
return 'orb';
if (rand < 0.6 + difficulty * 0.2)
return 'monolith';
return 'squeeze';
}
createMonolith(chunk, x, y, z) {
const width = 2 + Math.random() * 3;
const depth = 2 + Math.random() * 3;
const height = 5 + Math.random() * 10;
const geo = new THREE.BoxGeometry(width, height, depth);
const mat = new THREE.MeshPhongMaterial({
color: this.colors.monolith,
flatShading: true
});
const monolith = new THREE.Mesh(geo, mat);
monolith.position.set(x, y + height / 2, z);
monolith.castShadow = true;
monolith.receiveShadow = true;
const boundingBox = new THREE.Box3().setFromObject(monolith);
chunk.meshes.push(monolith);
chunk.obstacles.push({
mesh: monolith,
type: 'monolith',
boundingBox,
collected: false
});
}
createSqueeze(chunk, x, y, z) {
// Create a narrow passage with two blocks
const blockWidth = 1.5;
const blockHeight = 8;
const gapSize = 3 + Math.random() * 2;
const geo = new THREE.BoxGeometry(blockWidth, blockHeight, 4);
const mat = new THREE.MeshPhongMaterial({
color: this.colors.monolith,
flatShading: true
});
const leftBlock = new THREE.Mesh(geo, mat);
leftBlock.position.set(x - gapSize / 2 - blockWidth / 2, y + blockHeight / 2, z);
leftBlock.castShadow = true;
const rightBlock = new THREE.Mesh(geo, mat);
rightBlock.position.set(x + gapSize / 2 + blockWidth / 2, y + blockHeight / 2, z);
rightBlock.castShadow = true;
const leftBox = new THREE.Box3().setFromObject(leftBlock);
const rightBox = new THREE.Box3().setFromObject(rightBlock);
chunk.meshes.push(leftBlock, rightBlock);
chunk.obstacles.push({
mesh: leftBlock,
type: 'squeeze',
boundingBox: leftBox,
collected: false
});
chunk.obstacles.push({
mesh: rightBlock,
type: 'squeeze',
boundingBox: rightBox,
collected: false
});
}
createOrb(chunk, x, y, z) {
const radius = 0.5;
const geo = new THREE.SphereGeometry(radius, 16, 16);
const mat = new THREE.MeshBasicMaterial({
color: this.colors.orb,
transparent: true,
opacity: 0.9
});
const orb = new THREE.Mesh(geo, mat);
orb.position.set(x, y, z);
const boundingBox = new THREE.Box3(new THREE.Vector3(x - radius, y - radius, z - radius), new THREE.Vector3(x + radius, y + radius, z + radius));
chunk.meshes.push(orb);
chunk.obstacles.push({
mesh: orb,
type: 'orb',
boundingBox,
collected: false
});
}
update(playerZ) {
// Remove chunks that are behind the player
while (this.chunks.length > 0 && this.chunks[0].zPosition > playerZ + 100) {
const oldChunk = this.chunks.shift();
if (oldChunk) {
// Clean up meshes
for (const mesh of oldChunk.meshes) {
mesh.geometry.dispose?.();
if (Array.isArray(mesh.material)) {
for (const mat of mesh.material)
mat.dispose();
}
else {
mesh.material.dispose();
}
}
}
}
// Add new chunks as needed
const lastChunkZ = this.chunks[this.chunks.length - 1]?.zPosition || 0;
while (playerZ < lastChunkZ + this.chunkLength * 3) {
this.generateChunk();
}
}
getAllMeshes() {
const meshes = [];
for (const chunk of this.chunks) {
meshes.push(...chunk.meshes);
}
return meshes;
}
getObstacles() {
const obstacles = [];
for (const chunk of this.chunks) {
obstacles.push(...chunk.obstacles.filter(o => !o.collected));
}
return obstacles;
}
checkCollision(playerPos, playerRadius = 1) {
const obstacles = this.getObstacles();
let closestDistance = Infinity;
let collidingObstacle;
for (const obstacle of obstacles) {
const dist = this.distanceToPoint(obstacle.boundingBox, playerPos);
if (dist < playerRadius) {
collidingObstacle = obstacle;
}
if (dist < closestDistance) {
closestDistance = dist;
}
}
return {
collided: collidingObstacle !== undefined,
obstacle: collidingObstacle,
distance: closestDistance
};
}
distanceToPoint(box, point) {
const closestX = Math.max(box.min.x, Math.min(point.x, box.max.x));
const closestY = Math.max(box.min.y, Math.min(point.y, box.max.y));
const closestZ = Math.max(box.min.z, Math.min(point.z, box.max.z));
const dx = point.x - closestX;
const dy = point.y - closestY;
const dz = point.z - closestZ;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
checkOrbCollection(playerPos) {
const obstacles = this.getObstacles();
for (const obstacle of obstacles) {
if (obstacle.type === 'orb' && !obstacle.collected) {
const dist = this.distanceToPoint(obstacle.boundingBox, playerPos);
if (dist < 1.5) {
obstacle.collected = true;
return obstacle;
}
}
}
return null;
}
cleanup() {
for (const chunk of this.chunks) {
for (const mesh of chunk.meshes) {
mesh.geometry.dispose?.();
if (Array.isArray(mesh.material)) {
for (const mat of mesh.material)
mat.dispose();
}
else {
mesh.material.dispose();
}
}
}
this.chunks = [];
}
}
exports.World = World;
exports.world = new World();
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
// Procedural audio using Web Audio API
Object.defineProperty(exports, "__esModule", { value: true });
exports.audio = exports.AudioSystem = void 0;
class AudioSystem {
constructor() {
this.context = null;
this.engineOscillator = null;
this.engineGain = null;
this.isInitialized = false;
this.baseEngineFreq = 80;
this.currentSpeedRatio = 1;
// Will be initialized on first user interaction
}
init() {
if (this.isInitialized)
return;
const AudioCtx = (window.AudioContext || window.webkitAudioContext);
if (!AudioCtx)
return;
this.context = new AudioCtx();
this.createEngineSound();
this.isInitialized = true;
}
createEngineSound() {
if (!this.context)
return;
// Engine drone - low frequency oscillator
this.engineOscillator = this.context.createOscillator();
this.engineOscillator.type = 'sawtooth';
this.engineOscillator.frequency.value = this.baseEngineFreq;
// Lowpass filter to muffle the harsh sawtooth
const filter = this.context.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 400;
// Gain for volume control
this.engineGain = this.context.createGain();
this.engineGain.gain.value = 0;
// Connect nodes
this.engineOscillator.connect(filter);
filter.connect(this.engineGain);
this.engineGain.connect(this.context.destination);
this.engineOscillator.start();
}
setSpeed(speed) {
if (!this.engineOscillator || !this.context)
return;
const targetRatio = Math.max(0.5, Math.min(3, speed / 100));
this.currentSpeedRatio += (targetRatio - this.currentSpeedRatio) * 0.1;
// Modulate pitch based on speed
const targetFreq = this.baseEngineFreq * this.currentSpeedRatio;
this.engineOscillator.frequency.setTargetAtTime(targetFreq, this.context.currentTime, 0.1);
// Slight FM modulation for engine texture
if (this.context) {
const modFreq = 10 + Math.random() * 20;
this.engineOscillator.frequency.setValueAtTime(targetFreq + Math.sin(Date.now() * 0.01) * modFreq, this.context.currentTime);
}
}
setEngineVolume(volume) {
if (!this.engineGain || !this.context)
return;
this.engineGain.gain.setTargetAtTime(volume * 0.15, this.context.currentTime, 0.1);
}
playCollectible() {
if (!this.context)
return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(1760, this.context.currentTime + 0.1);
gain.gain.setValueAtTime(0.3, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.3);
osc.connect(gain);
gain.connect(this.context.destination);
osc.start();
osc.stop(this.context.currentTime + 0.3);
}
playNearMiss(multiplier) {
if (!this.context)
return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
// Whoosh sound - frequency sweep down
osc.type = 'triangle';
osc.frequency.setValueAtTime(200 + multiplier * 50, this.context.currentTime);
osc.frequency.exponentialRampToValueAtTime(100, this.context.currentTime + 0.2);
gain.gain.setValueAtTime(0.2 * multiplier, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.2);
osc.connect(gain);
gain.connect(this.context.destination);
osc.start();
osc.stop(this.context.currentTime + 0.2);
}
playBoost() {
if (!this.context)
return;
const osc = this.context.createOscillator();
const gain = this.context.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, this.context.currentTime);
osc.frequency.linearRampToValueAtTime(300, this.context.currentTime + 0.5);
gain.gain.setValueAtTime(0.2, this.context.currentTime);
gain.gain.linearRampToValueAtTime(0.1, this.context.currentTime + 0.5);
osc.connect(gain);
gain.connect(this.context.destination);
osc.start();
osc.stop(this.context.currentTime + 0.5);
}
playCrash() {
if (!this.context)
return;
// Noise burst for crash
const bufferSize = this.context.sampleRate * 0.5;
const buffer = this.context.createBuffer(1, bufferSize, this.context.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * Math.exp(-i / (bufferSize * 0.1));
}
const noise = this.context.createBufferSource();
noise.buffer = buffer;
const gain = this.context.createGain();
gain.gain.setValueAtTime(0.5, this.context.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.context.currentTime + 0.5);
noise.connect(gain);
gain.connect(this.context.destination);
noise.start();
}
pause() {
if (this.context && this.context.state === 'running') {
this.context.suspend();
}
}
resume() {
if (this.context && this.context.state === 'suspended') {
this.context.resume();
}
}
stop() {
if (this.engineOscillator && this.context) {
this.engineOscillator.stop();
this.engineOscillator = null;
}
this.setEngineVolume(0);
}
}
exports.AudioSystem = AudioSystem;
exports.audio = new AudioSystem();
};
// ── module: src/particles.ts ──
__mods["src/particles.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.particles = exports.ParticleSystem = void 0;
const state_1 = require("./state");
class ParticleSystem {
constructor() {
this.dustParticles = [];
this.debrisParticles = [];
this.sparkParticles = [];
this.dustGeometry = null;
this.debrisGeometry = null;
this.sparkGeometry = null;
this.dustPoints = null;
this.debrisPoints = null;
this.sparkPoints = null;
this.maxDustParticles = 200;
this.maxDebrisParticles = 50;
this.maxSparkParticles = 30;
this.initDustSystem();
this.initDebrisSystem();
this.initSparkSystem();
}
initDustSystem() {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(this.maxDustParticles * 3);
const sizes = new Float32Array(this.maxDustParticles);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const material = new THREE.PointsMaterial({
color: 0x8B7355,
size: 0.5,
transparent: true,
opacity: 0.4,
blending: THREE.AdditiveBlending || 1
});
this.dustGeometry = geometry;
this.dustPoints = new THREE.Points(geometry, material);
this.dustPoints.visible = false;
// Initialize dust particles
for (let i = 0; i < this.maxDustParticles; i++) {
this.dustParticles.push({
position: new THREE.Vector3((Math.random() - 0.5) * 60, Math.random() * 10, -Math.random() * 200),
velocity: new THREE.Vector3((Math.random() - 0.5) * 2, (Math.random() - 0.5) * 1, Math.random() * 50 + 20),
life: 1,
maxLife: 1,
size: Math.random() * 0.5 + 0.2,
color: new THREE.Color(0x8B7355)
});
}
}
initDebrisSystem() {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(this.maxDebrisParticles * 3);
const colors = new Float32Array(this.maxDebrisParticles * 3);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.3,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending || 1
});
this.debrisGeometry = geometry;
this.debrisPoints = new THREE.Points(geometry, material);
this.debrisPoints.visible = false;
}
initSparkSystem() {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(this.maxSparkParticles * 3);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({
color: 0x00ffff,
size: 0.2,
transparent: true,
opacity: 0.9,
blending: THREE.AdditiveBlending || 1
});
this.sparkGeometry = geometry;
this.sparkPoints = new THREE.Points(geometry, material);
this.sparkPoints.visible = false;
}
update(deltaTime, playerPosition) {
const state = (0, state_1.getState)();
const speedRatio = state.speed / 150;
this.updateDust(deltaTime, playerPosition, speedRatio);
this.updateDebris(deltaTime, playerPosition, speedRatio);
this.updateSparks(deltaTime, playerPosition, speedRatio);
}
updateDust(deltaTime, playerPos, speedRatio) {
if (!this.dustGeometry || !this.dustPoints)
return;
const positions = this.dustGeometry.attributes.position.array;
for (let i = 0; i < this.dustParticles.length; i++) {
const p = this.dustParticles[i];
// Move particle towards player (simulating forward motion)
p.position.z += p.velocity.z * deltaTime;
p.position.x += p.velocity.x * deltaTime;
p.position.y += p.velocity.y * deltaTime;
// Reset if behind player
if (p.position.z > playerPos.z + 10) {
p.position.z = playerPos.z - 200;
p.position.x = (Math.random() - 0.5) * 60;
p.position.y = Math.random() * 10;
}
// Position relative to camera view
const idx = i * 3;
positions[idx] = p.position.x;
positions[idx + 1] = p.position.y;
positions[idx + 2] = p.position.z;
}
(this.dustGeometry.attributes.position).needsUpdate = true;
this.dustPoints.visible = true;
}
updateDebris(deltaTime, playerPos, speedRatio) {
if (!this.debrisGeometry || !this.debrisPoints)
return;
// Spawn new debris based on speed
if (Math.random() < speedRatio * 0.3) {
this.spawnDebris(playerPos);
}
const positions = this.debrisGeometry.attributes.position.array;
const colors = this.debrisGeometry.attributes.color.array;
let activeCount = 0;
for (const p of this.debrisParticles) {
if (p.life <= 0)
continue;
p.life -= deltaTime;
p.position.add(p.velocity.clone().multiplyScalar(deltaTime));
const idx = activeCount * 3;
positions[idx] = p.position.x;
positions[idx + 1] = p.position.y;
positions[idx + 2] = p.position.z;
colors[idx] = p.color.r;
colors[idx + 1] = p.color.g;
colors[idx + 2] = p.color.b;
activeCount++;
}
// Update array size based on active particles
if (activeCount > 0) {
this.debrisGeometry.setDrawRange?.(0, activeCount);
(this.debrisGeometry.attributes.position).needsUpdate = true;
(this.debrisGeometry.attributes.color).needsUpdate = true;
this.debrisPoints.visible = true;
}
else {
this.debrisPoints.visible = false;
}
}
spawnDebris(playerPos) {
const pIndex = this.debrisParticles.findIndex(p => p.life <= 0);
if (pIndex === -1)
return;
const p = this.debrisParticles[pIndex];
p.position.copy(playerPos);
p.position.x += (Math.random() - 0.5) * 40;
p.position.y += Math.random() * 15;
p.position.z -= Math.random() * 5;
p.velocity.set((Math.random() - 0.5) * 10, (Math.random() - 0.5) * 5, Math.random() * 30 + 20);
p.life = 1;
p.color.setHex(Math.random() > 0.5 ? 0x8B4513 : 0x5D2906);
}
updateSparks(deltaTime, playerPos, speedRatio) {
if (!this.sparkGeometry || !this.sparkPoints)
return;
// Spawn sparks when boosting or at high speed
if (speedRatio > 0.8 && Math.random() < 0.5) {
this.spawnSpark(playerPos);
}
const positions = this.sparkGeometry.attributes.position.array;
let activeCount = 0;
for (const p of this.sparkParticles) {
if (p.life <= 0)
continue;
p.life -= deltaTime;
p.position.add(p.velocity.clone().multiplyScalar(deltaTime));
const idx = activeCount * 3;
positions[idx] = p.position.x;
positions[idx + 1] = p.position.y;
positions[idx + 2] = p.position.z;
activeCount++;
}
if (activeCount > 0) {
this.sparkGeometry.setDrawRange?.(0, activeCount);
(this.sparkGeometry.attributes.position).needsUpdate = true;
this.sparkPoints.visible = true;
}
else {
this.sparkPoints.visible = false;
}
}
spawnSpark(playerPos) {
const pIndex = this.sparkParticles.findIndex(p => p.life <= 0);
if (pIndex === -1)
return;
const p = this.sparkParticles[pIndex];
p.position.copy(playerPos);
p.position.x += (Math.random() - 0.5) * 3;
p.position.y += (Math.random() - 0.5) * 2;
p.position.z += 1;
p.velocity.set((Math.random() - 0.5) * 5, (Math.random() - 0.5) * 3, Math.random() * 20 + 10);
p.life = 0.3;
}
addExplosion(position, count = 10) {
for (let i = 0; i < count; i++) {
const pIndex = this.debrisParticles.findIndex(p => p.life <= 0);
if (pIndex === -1)
break;
const p = this.debrisParticles[pIndex];
p.position.copy(position);
p.velocity.set((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20);
p.life = 0.5;
p.color.setHex(0xff4400);
}
}
getDustPoints() {
return this.dustPoints;
}
getDebrisPoints() {
return this.debrisPoints;
}
getSparkPoints() {
return this.sparkPoints;
}
}
exports.ParticleSystem = ParticleSystem;
exports.particles = new ParticleSystem();
};
// ── module: src/hud.ts ──
__mods["src/hud.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.hud = exports.HUD = void 0;
const state_1 = require("./state");
class HUD {
constructor() {
this.speedBar = null;
this.speedText = null;
this.multiplierValue = null;
this.energyFill = null;
this.boostFill = null;
this.scoreValue = null;
this.startScreen = null;
this.gameOverScreen = null;
this.finalScore = null;
this.maxSpeedDisplay = null;
this.restartBtn = null;
this.startBtn = null;
this.cacheElements();
this.setupEventListeners();
}
cacheElements() {
this.speedBar = document.getElementById('speed-bar');
this.speedText = document.getElementById('speed-text');
this.multiplierValue = document.getElementById('mult-value');
this.energyFill = document.getElementById('energy-fill');
this.boostFill = document.getElementById('boost-fill');
this.scoreValue = document.getElementById('score-value');
this.startScreen = document.getElementById('start-screen');
this.gameOverScreen = document.getElementById('game-over');
this.finalScore = document.getElementById('final-score');
this.maxSpeedDisplay = document.getElementById('max-speed-display');
this.restartBtn = document.getElementById('restart-btn');
this.startBtn = document.getElementById('start-btn');
}
setupEventListeners() {
if (this.startBtn) {
this.startBtn.addEventListener('click', () => {
this.onStartClick();
});
}
if (this.restartBtn) {
this.restartBtn.addEventListener('click', () => {
this.onRestartClick();
});
}
}
onStartClick() {
const event = new CustomEvent('gameStart');
window.dispatchEvent(event);
}
onRestartClick() {
const event = new CustomEvent('gameRestart');
window.dispatchEvent(event);
}
update() {
const state = (0, state_1.getState)();
// Update speedometer
if (this.speedBar && this.speedText) {
const speedPct = Math.min(100, (state.speed / 200) * 100);
this.speedBar.style.setProperty('--speed-pct', `${speedPct}%`);
this.speedText.textContent = `${state.speed} km/h`;
}
// Update multiplier
if (this.multiplierValue) {
const mult = (0, state_1.getMultiplier)();
this.multiplierValue.textContent = `${mult.toFixed(1)}x`;
}
// Update energy bar
if (this.energyFill) {
this.energyFill.style.width = `${state.energy}%`;
// Change color based on energy level
if (state.energy < 20) {
this.energyFill.style.background = 'linear-gradient(90deg, #ff0000, #ff6600)';
}
else if (state.energy < 50) {
this.energyFill.style.background = 'linear-gradient(90deg, #ffaa00, #ffff00)';
}
else {
this.energyFill.style.background = 'linear-gradient(90deg, #00d4ff, #00ff88)';
}
}
// Update boost meter
if (this.boostFill) {
this.boostFill.style.width = `${state.boostAmount}%`;
if (state.boostAmount >= 100) {
this.boostFill.style.boxShadow = '0 0 30px rgba(255, 0, 102, 0.8)';
}
else {
this.boostFill.style.boxShadow = '0 0 15px rgba(255, 0, 102, 0.4)';
}
}
// Update score
if (this.scoreValue) {
this.scoreValue.textContent = Math.floor(state.score).toLocaleString();
}
}
pulseMultiplier() {
if (this.multiplierValue) {
this.multiplierValue.classList.remove('pulse');
void this.multiplierValue.offsetWidth; // Trigger reflow
this.multiplierValue.classList.add('pulse');
}
}
showStartScreen() {
if (this.startScreen) {
this.startScreen.classList.remove('hidden');
}
if (this.gameOverScreen) {
this.gameOverScreen.classList.add('hidden');
}
}
hideStartScreen() {
if (this.startScreen) {
this.startScreen.classList.add('hidden');
}
}
showGameOver(score, maxSpeed) {
if (this.gameOverScreen) {
this.gameOverScreen.classList.remove('hidden');
}
if (this.finalScore) {
this.finalScore.textContent = `Score: ${Math.floor(score).toLocaleString()}`;
}
if (this.maxSpeedDisplay) {
this.maxSpeedDisplay.textContent = `Max Speed: ${maxSpeed} km/h`;
}
}
hideGameOver() {
if (this.gameOverScreen) {
this.gameOverScreen.classList.add('hidden');
}
}
setBoostReady(ready) {
if (this.boostFill) {
if (ready) {
this.boostFill.style.boxShadow = '0 0 30px rgba(255, 0, 102, 0.9), 0 0 60px rgba(255, 107, 53, 0.6)';
this.boostFill.style.animation = 'pulse-glow 0.5s ease-in-out infinite';
}
else {
this.boostFill.style.boxShadow = '0 0 15px rgba(255, 0, 102, 0.4)';
this.boostFill.style.animation = '';
}
}
}
}
exports.HUD = HUD;
exports.hud = new HUD();
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.5-122b-a10b","provider":"openrouter","persona":"typescript","sessionId":"e82167cf-abec-4609-a233-2ad86c1172db","tokensIn":3000997,"tokensOut":31282,"tokensTotal":3032279,"cost":0.84532578,"turns":79,"toolCalls":81,"failedToolCalls":0,"timestamp":"2026-08-05T05:47:47.832Z"} -->