Canyon Run
Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP GGUF · 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 60,232 bytes · SHA-256 fffb30acb445
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Canyon Racer - High-Speed Flow</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style>
/* ── css: layout ── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #0a0515;
font-family: 'Segoe UI', Arial, sans-serif;
user-select: none;
}
#app {
width: 100%;
height: 100%;
position: relative;
}
#game-canvas {
width: 100%;
height: 100%;
}
/* ── css: hud ── */
#hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
#score-container {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font-size: 36px;
font-weight: 300;
letter-spacing: 4px;
text-shadow: 0 0 20px rgba(255, 150, 50, 0.8);
}
#multiplier-container {
position: absolute;
top: 65px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
#multiplier-value {
color: #ffaa44;
font-size: 24px;
font-weight: 600;
letter-spacing: 2px;
text-shadow: 0 0 15px rgba(255, 170, 68, 0.6);
}
#near-miss-text {
color: #ff3366;
font-size: 20px;
font-weight: 700;
letter-spacing: 6px;
opacity: 0;
text-shadow: 0 0 10px rgba(255, 51, 102, 0.9);
transition: opacity 0.1s;
}
#near-miss-text.show {
opacity: 1;
animation: pulse-near-miss 0.4s ease-out;
}
@keyframes pulse-near-miss {
0% { transform: scale(0.8); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
#speed-container {
position: absolute;
right: 30px;
bottom: 30px;
width: 200px;
}
#speed-label {
color: rgba(255, 255, 255, 0.6);
font-size: 10px;
letter-spacing: 3px;
margin-bottom: 4px;
}
#speed-bar {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
#speed-fill {
height: 100%;
width: 50%;
background: linear-gradient(90deg, #ffaa44, #ff4466);
border-radius: 2px;
box-shadow: 0 0 10px rgba(255, 100, 50, 0.5);
transition: width 0.15s ease-out;
}
#energy-container {
position: absolute;
left: 30px;
bottom: 30px;
width: 160px;
}
#energy-label {
color: rgba(255, 255, 255, 0.6);
font-size: 10px;
letter-spacing: 3px;
margin-top: 4px;
}
#energy-bar {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
#energy-fill {
height: 100%;
width: 80%;
background: linear-gradient(90deg, #44ffaa, #88ffff);
border-radius: 2px;
box-shadow: 0 0 10px rgba(100, 255, 200, 0.5);
transition: width 0.15s ease-out;
}
#turbo-container {
position: absolute;
left: 50%;
bottom: 20px;
transform: translateX(-50%);
width: 250px;
}
#turbo-label {
color: rgba(255, 255, 255, 0.6);
font-size: 10px;
letter-spacing: 3px;
text-align: center;
margin-top: 4px;
}
#turbo-bar {
width: 100%;
height: 3px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
}
#turbo-fill {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #ff6600, #ffcc00);
border-radius: 2px;
box-shadow: 0 0 10px rgba(255, 200, 0, 0.5);
transition: width 0.15s ease-out;
}
#turbo-fill.active {
background: linear-gradient(90deg, #ffff00, #ff4400);
box-shadow: 0 0 20px rgba(255, 255, 0, 0.9);
}
/* ── css: screens ── */
#start-screen, #game-over {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 20;
pointer-events: all;
}
#start-screen {
background: radial-gradient(ellipse at center, rgba(30, 15, 50, 0.9) 0%, rgba(10, 5, 20, 0.97) 100%);
}
#game-over {
background: radial-gradient(ellipse at center, rgba(50, 10, 20, 0.92) 0%, rgba(10, 5, 20, 0.97) 100%);
}
.hidden {
display: none !important;
}
#game-title {
font-size: 72px;
font-weight: 200;
color: #fff;
letter-spacing: 16px;
text-shadow: 0 0 40px rgba(255, 150, 50, 0.6), 0 0 80px rgba(255, 80, 30, 0.3);
margin-bottom: 40px;
}
#start-hint {
color: rgba(255, 255, 255, 0.7);
font-size: 18px;
letter-spacing: 4px;
margin-bottom: 10px;
}
#start-hint-small {
color: rgba(255, 255, 255, 0.4);
font-size: 13px;
letter-spacing: 2px;
margin-bottom: 50px;
}
#start-btn, #restart-btn {
padding: 16px 60px;
font-size: 18px;
font-weight: 300;
letter-spacing: 8px;
color: #fff;
background: transparent;
border: 1px solid rgba(255, 150, 50, 0.5);
cursor: pointer;
transition: all 0.3s ease;
pointer-events: all;
}
#start-btn:hover, #restart-btn:hover {
background: rgba(255, 150, 50, 0.15);
border-color: rgba(255, 150, 50, 0.9);
box-shadow: 0 0 30px rgba(255, 150, 50, 0.3);
}
#game-over-title {
font-size: 60px;
font-weight: 200;
color: #ff4466;
letter-spacing: 12px;
text-shadow: 0 0 30px rgba(255, 68, 102, 0.6);
margin-bottom: 40px;
}
#final-score, #max-speed-display {
color: rgba(255, 255, 255, 0.8);
font-size: 22px;
letter-spacing: 4px;
margin-bottom: 10px;
}
#restart-btn {
margin-top: 40px;
}
</style>
</head>
<body>
<div id="game-canvas"></div>
<div id="hud">
<div id="speed-container">
<div id="speed-label">SPEED</div>
<div id="speed-bar"><div id="speed-fill"></div></div>
</div>
<div id="multiplier-container">
<div id="multiplier-value">x1.0</div>
<div id="near-miss-text">NEAR MISS!</div>
</div>
<div id="score-container">
<div id="score-value">0</div>
</div>
<div id="energy-container">
<div id="energy-bar"><div id="energy-fill"></div></div>
<div id="energy-label">ENERGY</div>
</div>
<div id="turbo-container">
<div id="turbo-bar"><div id="turbo-fill"></div></div>
<div id="turbo-label">TURBO</div>
</div>
</div>
<div id="game-over" class="hidden">
<div id="game-over-title">CRASHED</div>
<div id="final-score">Score: 0</div>
<div id="max-speed-display">Max Speed: 0</div>
<div id="restart-btn">RESTART</div>
</div>
<div id="start-screen">
<div id="game-title">CANYON RACER</div>
<div id="start-hint">Move mouse or use arrow keys to steer</div>
<div id="start-hint-small">Collect orbs for energy · Hold Shift for turbo</div>
<div id="start-btn">START</div>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./input":"src/input.ts","./player":"src/player.ts","./game":"src/game.ts","./world":"src/world.ts","./renderer":"src/renderer.ts","./audio":"src/audio.ts","./types":"src/types.ts"},"src/input.ts":{"./types":"src/types.ts"},"src/audio.ts":{"./types":"src/types.ts"},"src/world.ts":{"./types":"src/types.ts"},"src/player.ts":{"./types":"src/types.ts"},"src/game.ts":{"./types":"src/types.ts"},"src/renderer.ts":{"./types":"src/types.ts"}};
function __require(id) {
if (__cache[id]) return __cache[id].exports;
var module = __cache[id] = { exports: {} };
var factory = __mods[id];
if (!factory) throw new Error("Module not found: " + id);
factory(module.exports, function (spec) {
var target = (__map[id] && __map[id][spec]) || spec;
return __require(target);
}, module);
return module.exports;
}
// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.main = main;
const input_1 = require("./input");
const player_1 = require("./player");
const game_1 = require("./game");
const world_1 = require("./world");
const renderer_1 = require("./renderer");
const audio_1 = require("./audio");
let state;
let metrics;
let player;
let input;
let world;
let chunks;
let lastChunkIndex;
let cameraState;
let audio;
let animationId = null;
let lastFrameTime = 0;
let inputCleanup = null;
function updateHUD() {
const scoreEl = document.getElementById('score-value');
const multEl = document.getElementById('multiplier-value');
const speedFill = document.getElementById('speed-fill');
const energyFill = document.getElementById('energy-fill');
const turboFill = document.getElementById('turbo-fill');
if (scoreEl)
scoreEl.textContent = Math.floor(metrics.score).toLocaleString();
if (multEl)
multEl.textContent = 'x' + metrics.multiplier.toFixed(1);
if (speedFill) {
const pct = Math.min(100, ((player.speed - 40) / (player.maxSpeed * 1.2)) * 100);
speedFill.style.width = pct + '%';
}
if (energyFill) {
const pct = Math.min(100, (player.energy / player.maxEnergy) * 100);
energyFill.style.width = pct + '%';
}
if (turboFill) {
const pct = player.turboCharge * 100;
turboFill.style.width = pct + '%';
if (player.turboActive) {
turboFill.classList.add('active');
}
else {
turboFill.classList.remove('active');
}
}
}
function showNearMiss() {
const el = document.getElementById('near-miss-text');
if (el) {
el.classList.remove('show');
void el.offsetWidth;
el.classList.add('show');
setTimeout(() => el.classList.remove('show'), 500);
}
}
function checkCollectibles() {
const playerRadius = 1.5;
for (let i = chunks.length - 1; i >= 0; i--) {
const chunk = chunks[i];
for (const col of chunk.collectibles) {
if (col.collected)
continue;
const dx = player.position.x - col.position.x;
const dy = player.position.y - col.position.y;
const dz = player.position.z - col.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < playerRadius + 1.2) {
col.collected = true;
if (col.type === 'energy') {
(0, player_1.addEnergy)(player, 20);
}
else {
(0, player_1.addTurboCharge)(player, 0.5);
}
(0, audio_1.playCollectSound)(audio);
const el = document.getElementById('near-miss-text');
if (el) {
el.textContent = col.type === 'turbo' ? 'TURBO!' : '+ENERGY';
el.classList.remove('show');
void el.offsetWidth;
el.classList.add('show');
setTimeout(() => {
el.textContent = 'NEAR MISS!';
el.classList.remove('show');
}, 500);
}
}
}
}
}
function updateWorld() {
const difficulty = (0, game_1.getDifficulty)(metrics);
world.difficultyMultiplier = difficulty;
// Generate new chunks ahead
while ((0, world_1.shouldGenerateChunk)(lastChunkIndex, player.position.z, world)) {
lastChunkIndex++;
const chunk = (0, world_1.generateChunk)(lastChunkIndex, world, difficulty);
chunks.push(chunk);
(0, renderer_1.createChunkMesh)(chunk);
(0, renderer_1.createChunkCollectibleMeshes)(chunk);
}
// Remove old chunks
for (let i = 0; i < chunks.length; i++) {
if ((0, world_1.shouldDeactivateChunk)(chunks[i], player.position.z, world)) {
(0, renderer_1.removeChunkMesh)(chunks[i].id);
chunks.splice(i, 1);
i--;
}
}
}
function gameLoop(currentTime) {
if (!state.running)
return;
const dt = Math.min(0.05, (currentTime - lastFrameTime) / 1000);
lastFrameTime = currentTime;
// Update player
(0, player_1.updatePlayer)(player, input, dt, (0, game_1.getDifficulty)(metrics));
// Check for collectible pickups
checkCollectibles();
// Check collisions
const allColliders = [];
for (const chunk of chunks) {
allColliders.push(...chunk.colliders);
}
const collisionResult = (0, player_1.checkCollision)(player.position, 1.5, allColliders);
// Handle near-miss scoring
const scoringResult = (0, game_1.calculateScoring)(metrics, player, dt, collisionResult.nearMiss, collisionResult.minDist, collisionResult.dangerZones);
if (scoringResult.nearMissTriggered) {
(0, audio_1.playNearMissSound)(audio);
showNearMiss();
(0, renderer_1.spawnDebris)(player, 8);
cameraState.shakeIntensity = 0.3;
}
// Camera shake based on danger
if (collisionResult.dangerZones > 0) {
cameraState.shakeIntensity = Math.min(0.4, collisionResult.dangerZones * 0.05);
}
else {
cameraState.shakeIntensity *= 0.95;
}
// Turbo activation sound
if (player.turboActive && player.turboDuration < player.turboMaxDuration - 0.1) {
// sound already played on activation
}
if (input.boost && player.turboCharge >= 0.3 && !player.turboActive) {
(0, audio_1.playTurboSound)(audio);
}
// Collision
if (collisionResult.collided) {
handleGameOver();
return;
}
// Energy death
if (!(0, player_1.isPlayerAlive)(player)) {
handleGameOver();
return;
}
// Update systems
updateWorld();
(0, renderer_1.updatePlayerMesh)(player);
(0, renderer_1.updateCamera)(player, cameraState);
(0, renderer_1.updateCameraShake)(cameraState, cameraState.shakeIntensity);
(0, renderer_1.updateCollectibles)(dt);
(0, audio_1.updateEngineSound)(audio, player.speed, player.turboActive);
updateHUD();
// Render
(0, renderer_1.render)();
animationId = requestAnimationFrame(gameLoop);
}
function handleGameOver() {
state.gameOver = true;
state.running = false;
(0, audio_1.playCrashSound)(audio);
if (animationId) {
cancelAnimationFrame(animationId);
}
// Show game over screen
const gameOverEl = document.getElementById('game-over');
const finalScoreEl = document.getElementById('final-score');
const maxSpeedEl = document.getElementById('max-speed-display');
if (gameOverEl)
gameOverEl.classList.remove('hidden');
if (finalScoreEl)
finalScoreEl.textContent = 'Score: ' + Math.floor(metrics.score).toLocaleString();
if (maxSpeedEl)
maxSpeedEl.textContent = 'Max Speed: ' + Math.floor(metrics.maxSpeed) + ' km/h';
}
function startGame() {
// Reset state
(0, game_1.resetGame)(metrics, player);
state.gameOver = false;
state.running = true;
state.started = true;
// Reset world
chunks = [];
lastChunkIndex = -1;
// Hide screens
const startScreen = document.getElementById('start-screen');
const gameOverEl = document.getElementById('game-over');
if (startScreen)
startScreen.classList.add('hidden');
if (gameOverEl)
gameOverEl.classList.add('hidden');
// Init audio
(0, audio_1.initAudio)(audio);
(0, audio_1.resumeAudio)(audio);
// Start loop
lastFrameTime = performance.now();
animationId = requestAnimationFrame(gameLoop);
}
function setupRestartButton() {
const restartBtn = document.getElementById('restart-btn');
if (restartBtn) {
restartBtn.addEventListener('click', () => {
if (animationId)
cancelAnimationFrame(animationId);
startGame();
});
}
}
function setupStartButton() {
const startBtn = document.getElementById('start-btn');
if (startBtn) {
startBtn.addEventListener('click', () => {
startGame();
});
}
}
function main() {
// Initialize everything
state = (0, game_1.createGameState)();
metrics = (0, game_1.createGameMetrics)();
player = (0, player_1.createPlayer)();
input = (0, input_1.createInput)();
world = (0, world_1.createWorldParams)();
chunks = [];
lastChunkIndex = -1;
cameraState = {
shakeIntensity: 0,
shakeDecay: 0.95,
shakeVector: { x: 0, y: 0, z: 0 },
targetFov: 70,
};
audio = (0, audio_1.createAudio)();
// Setup UI
(0, input_1.setupInputEvents)(input);
setupStartButton();
setupRestartButton();
// Setup renderer
try {
(0, renderer_1.initRenderer)('game-canvas');
}
catch (e) {
console.error('Failed to init renderer:', e);
return;
}
// Generate initial chunks
for (let i = 0; i < 5; i++) {
lastChunkIndex++;
const chunk = (0, world_1.generateChunk)(lastChunkIndex, world, 1);
chunks.push(chunk);
(0, renderer_1.createChunkMesh)(chunk);
(0, renderer_1.createChunkCollectibleMeshes)(chunk);
}
console.log('Canyon Racer loaded. Press Start or click START button.');
}
main();
};
// ── module: src/types.ts ──
__mods["src/types.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PALETTE = void 0;
exports.PALETTE = {
skyTop: 0x1a0a2e,
skyBottom: 0xff4422,
wallColor1: 0x3d1f3a,
wallColor2: 0x2a1528,
wallColor3: 0x4a2845,
accentOrange: 0xff8833,
accentPurple: 0x8833ff,
accentCyan: 0x33ffff,
energyColor: 0x44ffaa,
turboColor: 0xffcc00,
dangerColor: 0xff3366,
};
};
// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInput = createInput;
exports.setupInputEvents = setupInputEvents;
function createInput() {
return {
left: false,
right: false,
up: false,
down: false,
boost: false,
mouseX: 0,
mouseY: 0,
usingMouse: false,
};
}
function setupInputEvents(input) {
const keyDown = (e) => {
switch (e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
input.left = true;
break;
case 'ArrowRight':
case 'd':
case 'D':
input.right = true;
break;
case 'ArrowUp':
case 'w':
case 'W':
input.up = true;
break;
case 'ArrowDown':
case 's':
case 'S':
input.down = true;
break;
case 'Shift':
input.boost = true;
break;
}
};
const keyUp = (e) => {
switch (e.key) {
case 'ArrowLeft':
case 'a':
case 'A':
input.left = false;
break;
case 'ArrowRight':
case 'd':
case 'D':
input.right = false;
break;
case 'ArrowUp':
case 'w':
case 'W':
input.up = false;
break;
case 'ArrowDown':
case 's':
case 'S':
input.down = false;
break;
case 'Shift':
input.boost = false;
break;
}
};
const mouseMove = (e) => {
input.usingMouse = true;
const cx = window.innerWidth / 2;
const cy = window.innerHeight / 2;
input.mouseX = (e.clientX - cx) / cx;
input.mouseY = (e.clientY - cy) / cy;
};
document.addEventListener('keydown', keyDown);
document.addEventListener('keyup', keyUp);
document.addEventListener('mousemove', mouseMove);
return () => {
document.removeEventListener('keydown', keyDown);
document.removeEventListener('keyup', keyUp);
document.removeEventListener('mousemove', mouseMove);
};
}
};
// ── module: src/audio.ts ──
__mods["src/audio.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAudio = createAudio;
exports.initAudio = initAudio;
exports.resumeAudio = resumeAudio;
exports.updateEngineSound = updateEngineSound;
exports.playCollectSound = playCollectSound;
exports.playNearMissSound = playNearMissSound;
exports.playTurboSound = playTurboSound;
exports.playCrashSound = playCrashSound;
exports.cleanupAudio = cleanupAudio;
function createAudio() {
return {
ctx: null,
engineOsc: null,
engineGain: null,
engineFilter: null,
masterGain: null,
initialized: false,
};
}
function initAudio(audio) {
if (audio.initialized)
return;
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
if (!AudioContextClass)
return;
try {
audio.ctx = new AudioContextClass();
const ctx = audio.ctx;
audio.masterGain = ctx.createGain();
audio.masterGain.gain.value = 0.3;
audio.masterGain.connect(ctx.destination);
// Engine sound - low hum
audio.engineOsc = ctx.createOscillator();
audio.engineOsc.type = 'sawtooth';
audio.engineOsc.frequency.value = 80;
audio.engineFilter = ctx.createBiquadFilter();
audio.engineFilter.type = 'lowpass';
audio.engineFilter.frequency.value = 200;
audio.engineFilter.Q.value = 2;
audio.engineGain = ctx.createGain();
audio.engineGain.gain.value = 0;
audio.engineOsc.connect(audio.engineFilter);
audio.engineFilter.connect(audio.engineGain);
audio.engineGain.connect(audio.masterGain);
audio.engineOsc.start();
audio.initialized = true;
}
catch (e) {
console.warn('Audio init failed:', e);
}
}
function resumeAudio(audio) {
if (audio.ctx && audio.ctx.state === 'suspended') {
audio.ctx.resume();
}
}
function updateEngineSound(audio, speed, turboActive) {
if (!audio.initialized || !audio.engineOsc || !audio.engineGain || !audio.engineFilter || !audio.ctx)
return;
const ctx = audio.ctx;
const baseFreq = 60 + speed * 0.8;
const freq = turboActive ? baseFreq * 1.5 : baseFreq;
audio.engineOsc.frequency.linearRampToValueAtTime(freq, ctx.currentTime + 0.05);
const gain = turboActive ? 0.15 : 0.08;
audio.engineGain.gain.linearRampToValueAtTime(gain, ctx.currentTime + 0.05);
const filterFreq = turboActive ? 600 + speed : 300 + speed * 0.5;
audio.engineFilter.frequency.linearRampToValueAtTime(filterFreq, ctx.currentTime + 0.05);
}
function playCollectSound(audio) {
if (!audio.initialized || !audio.ctx || !audio.masterGain)
return;
const ctx = audio.ctx;
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1320, ctx.currentTime + 0.08);
osc.frequency.exponentialRampToValueAtTime(880, ctx.currentTime + 0.15);
gain.gain.setValueAtTime(0.2, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(audio.masterGain);
osc.start(ctx.currentTime);
osc.stop(ctx.currentTime + 0.2);
}
function playNearMissSound(audio) {
if (!audio.initialized || !audio.ctx || !audio.masterGain)
return;
const ctx = audio.ctx;
// Whoosh sound
const bufferSize = ctx.sampleRate * 0.3;
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
const t = i / bufferSize;
const envelope = Math.sin(t * Math.PI) * Math.exp(-t * 5);
data[i] = (Math.random() * 2 - 1) * envelope * 0.15;
}
const source = audio.ctx.createBufferSource();
source.buffer = buffer;
const filter = audio.ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 1000;
filter.Q.value = 0.5;
const gain = audio.ctx.createGain();
gain.gain.value = 0.5;
source.connect(filter);
filter.connect(gain);
gain.connect(audio.masterGain);
source.start();
}
function playTurboSound(audio) {
if (!audio.initialized || !audio.ctx || !audio.masterGain)
return;
const osc = audio.ctx.createOscillator();
const gain = audio.ctx.createGain();
const filter = audio.ctx.createBiquadFilter();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, audio.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(600, audio.ctx.currentTime + 0.3);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(300, audio.ctx.currentTime);
filter.frequency.exponentialRampToValueAtTime(2000, audio.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0, audio.ctx.currentTime);
gain.gain.linearRampToValueAtTime(0.15, audio.ctx.currentTime + 0.05);
gain.gain.linearRampToValueAtTime(0.05, audio.ctx.currentTime + 0.3);
osc.connect(filter);
filter.connect(gain);
gain.connect(audio.masterGain);
osc.start();
osc.stop(audio.ctx.currentTime + 0.4);
}
function playCrashSound(audio) {
if (!audio.initialized || !audio.ctx || !audio.masterGain)
return;
const bufferSize = audio.ctx.sampleRate * 0.5;
const buffer = audio.ctx.createBuffer(1, bufferSize, audio.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
const t = i / bufferSize;
const envelope = Math.exp(-t * 3);
data[i] = (Math.random() * 2 - 1) * envelope * 0.4;
}
const source = audio.ctx.createBufferSource();
source.buffer = buffer;
const filter = audio.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 500;
const gain = audio.ctx.createGain();
gain.gain.value = 0.6;
source.connect(filter);
filter.connect(gain);
gain.connect(audio.masterGain);
source.start();
}
function cleanupAudio(audio) {
if (audio.ctx) {
audio.ctx.close();
}
}
};
// ── module: src/world.ts ──
__mods["src/world.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWorldParams = createWorldParams;
exports.generateChunk = generateChunk;
exports.shouldDeactivateChunk = shouldDeactivateChunk;
exports.shouldGenerateChunk = shouldGenerateChunk;
let chunkIdCounter = 0;
function seededRandom(seed) {
let s = seed;
return () => {
s = (s * 16807) % 2147483647;
return (s - 1) / 2147483646;
};
}
function createWorldParams() {
return {
chunkLength: 200,
chunksActive: 8,
canyonWidth: 80,
canyonHeight: 60,
wallHeight: 80,
generationDistance: 800,
difficultyMultiplier: 1,
};
}
function generateChunkWalls(chunkZ, width, height, rand) {
const walls = [];
const numSegments = 4 + Math.floor(rand() * 3);
const segmentLength = 200 / numSegments;
for (let i = 0; i < numSegments; i++) {
const z = chunkZ + i * segmentLength;
const variation = (rand() - 0.5) * width * 0.2;
// Left wall
walls.push({
position: {
x: -width / 2 - 5 - variation,
y: -height / 2 + (rand() - 0.3) * 15,
z: z + segmentLength / 2,
},
size: {
x: 15 + rand() * 10,
y: 50 + rand() * 30,
z: segmentLength + 2,
},
type: 'left',
});
// Right wall
walls.push({
position: {
x: width / 2 + 5 + variation,
y: -height / 2 + (rand() - 0.3) * 15,
z: z + segmentLength / 2,
},
size: {
x: 15 + rand() * 10,
y: 50 + rand() * 30,
z: segmentLength + 2,
},
type: 'right',
});
}
return walls;
}
function generateChunkObstacles(chunkZ, chunkLength, width, difficulty, rand) {
const obstacles = [];
const count = 2 + Math.floor(rand() * 2) + Math.floor(difficulty * 0.8);
for (let i = 0; i < count; i++) {
const z = chunkZ + 40 + rand() * (chunkLength - 80);
const x = (rand() - 0.5) * (width * 0.6);
const y = (rand() - 0.5) * 25;
if (rand() < 0.4 && difficulty > 1.5) {
// Tight squeeze - two blocks with a gap
const gapWidth = Math.max(8, 18 - difficulty * 1.5);
obstacles.push({
position: { x: x - gapWidth / 2 - 4, y, z },
size: { x: 4, y: 12 + rand() * 6, z: 4 },
type: 'squeeze',
squeezeGap: gapWidth,
});
obstacles.push({
position: { x: x + gapWidth / 2 + 4, y, z },
size: { x: 4, y: 12 + rand() * 6, z: 4 },
type: 'squeeze',
squeezeGap: gapWidth,
});
}
else {
// Monolith
const height = 10 + rand() * 15;
obstacles.push({
position: { x, y, z },
size: {
x: 4 + rand() * 4,
y: height,
z: 4 + rand() * 4,
},
type: 'monolith',
});
}
}
return obstacles;
}
function generateChunkCollectibles(chunkZ, chunkLength, width, obstacles, rand) {
const collectibles = [];
const count = 5 + Math.floor(rand() * 4);
for (let i = 0; i < count; i++) {
let z = chunkZ + 20 + rand() * (chunkLength - 40);
let x = (rand() - 0.5) * width * 0.7;
let y = (rand() - 0.5) * 20;
// Sometimes place collectibles in tight spots (risk/reward)
if (rand() < 0.3 && obstacles.length > 0) {
const obstacle = obstacles[Math.floor(rand() * obstacles.length)];
x = obstacle.position.x + (rand() - 0.5) * 15;
y = obstacle.position.y + (rand() - 0.5) * 15;
z = obstacle.position.z - 5 - rand() * 10;
}
collectibles.push({
position: { x, y, z },
type: rand() < 0.85 ? 'energy' : 'turbo',
collected: false,
rotationSpeed: 1 + rand() * 2,
});
}
return collectibles;
}
function computeColliders(chunk) {
const colliders = [];
for (const wall of chunk.walls) {
colliders.push({
type: 'box',
center: wall.position,
halfSize: {
x: wall.size.x / 2,
y: wall.size.y / 2,
z: wall.size.z / 2,
},
owner: 'wall',
dangerZone: 8,
});
}
for (const obs of chunk.obstacles) {
colliders.push({
type: 'box',
center: obs.position,
halfSize: {
x: obs.size.x / 2,
y: obs.size.y / 2,
z: obs.size.z / 2,
},
owner: 'obstacle',
dangerZone: 6,
});
}
return colliders;
}
function generateChunk(chunkIndex, params, difficulty) {
const chunkZ = chunkIndex * params.chunkLength;
const seed = Math.abs(chunkIndex * 12345 + 67890);
const rand = seededRandom(seed);
const canyonWidth = params.canyonWidth - Math.min(difficulty * 2, 20);
const canyonHeight = params.canyonHeight - Math.min(difficulty, 10);
const walls = generateChunkWalls(chunkZ, canyonWidth, canyonHeight, rand);
const obstacles = generateChunkObstacles(chunkZ, params.chunkLength, canyonWidth, difficulty, rand);
const collectibles = generateChunkCollectibles(chunkZ, params.chunkLength, canyonWidth, obstacles, rand);
const id = chunkIdCounter++;
const chunkData = {
id,
position: { x: 0, y: 0, z: chunkZ },
walls,
obstacles,
collectibles,
active: true,
generated: true,
};
return {
...chunkData,
colliders: computeColliders(chunkData),
};
}
function shouldDeactivateChunk(chunk, playerZ, params) {
return chunk.position.z + params.chunkLength < playerZ - 300;
}
function shouldGenerateChunk(lastChunkIndex, playerZ, params) {
const lastChunkEnd = (lastChunkIndex + 1) * params.chunkLength;
return lastChunkEnd < playerZ + params.generationDistance;
}
};
// ── module: src/player.ts ──
__mods["src/player.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPlayer = createPlayer;
exports.updatePlayer = updatePlayer;
exports.addEnergy = addEnergy;
exports.addTurboCharge = addTurboCharge;
exports.isPlayerAlive = isPlayerAlive;
exports.checkCollision = checkCollision;
function createPlayer() {
return {
position: { x: 0, y: 0, z: 0 },
velocity: { x: 0, y: 0, z: 100 },
rotation: { x: 0, y: 0, z: 0 },
targetRotation: { x: 0, y: 0, z: 0 },
energy: 100,
maxEnergy: 100,
speed: 80,
baseSpeed: 80,
maxSpeed: 150,
turboCharge: 0,
turboActive: false,
turboDuration: 0,
turboMaxDuration: 3,
nearMissCooldown: 0.3,
lastNearMissTime: 0,
};
}
function updatePlayer(player, input, dt, difficulty) {
const accel = 60;
const turnSpeed = 55;
const liftSpeed = 45;
const drag = 0.98;
const energyDecay = 8 + difficulty * 2;
const maxMoveRange = 50;
// Apply input to target velocity
if (input.usingMouse) {
const targetXVel = input.mouseX * turnSpeed * 2;
const targetYVel = -input.mouseY * liftSpeed * 2;
player.velocity.x += (targetXVel - player.velocity.x) * 8 * dt;
player.velocity.y += (targetYVel - player.velocity.y) * 8 * dt;
}
else {
if (input.left)
player.velocity.x -= accel * dt;
if (input.right)
player.velocity.x += accel * dt;
if (input.up)
player.velocity.y += liftSpeed * dt;
if (input.down)
player.velocity.y -= liftSpeed * dt;
player.velocity.x *= drag;
player.velocity.y *= drag;
}
// Clamp lateral movement
player.position.x = Math.max(-maxMoveRange, Math.min(maxMoveRange, player.position.x));
player.position.y = Math.max(-maxMoveRange * 0.6, Math.min(maxMoveRange * 0.6, player.position.y));
// Speed management
const speedBoost = difficulty * 2;
player.baseSpeed = 80 + speedBoost;
player.maxSpeed = 150 + speedBoost * 0.5;
if (input.boost && player.turboCharge >= 0.3 && !player.turboActive) {
player.turboActive = true;
player.turboDuration = player.turboMaxDuration;
}
if (player.turboActive) {
player.turboDuration -= dt;
player.speed = Math.min(player.speed + 80 * dt, player.maxSpeed * 1.3);
player.turboCharge -= dt * 0.3;
if (player.turboDuration <= 0) {
player.turboActive = false;
player.turboCharge = 0;
}
}
else {
player.speed = Math.max(player.speed - 40 * dt, player.baseSpeed);
// Build turbo on straightaways and speed
if (Math.abs(player.velocity.x) < 5 && Math.abs(player.velocity.y) < 5) {
player.turboCharge = Math.min(player.turboCharge + dt * 0.15, 1);
}
}
// Apply forward movement
const moveZ = player.speed * dt;
player.position.z += moveZ;
player.position.x += player.velocity.x * dt;
player.position.y += player.velocity.y * dt;
// Banking rotation (smooth)
player.targetRotation.z = -player.velocity.x * 0.025;
player.targetRotation.x = -player.velocity.y * 0.02;
// Pitch up during turbo
if (player.turboActive) {
player.targetRotation.x -= 0.15;
}
player.rotation.z += (player.targetRotation.z - player.rotation.z) * 6 * dt;
player.rotation.x += (player.targetRotation.x - player.rotation.x) * 6 * dt;
player.rotation.y = player.velocity.x * 0.003;
// Energy decay
player.energy -= energyDecay * dt;
// Near miss cooldown
if (player.nearMissCooldown > 0) {
player.nearMissCooldown -= dt;
}
}
function addEnergy(player, amount) {
player.energy = Math.min(player.energy + amount, player.maxEnergy);
}
function addTurboCharge(player, amount) {
player.turboCharge = Math.min(player.turboCharge + amount, 1);
}
function isPlayerAlive(player) {
return player.energy > 0;
}
function checkCollision(playerPos, playerRadius, colliders) {
let collided = false;
let nearMiss = false;
let minDist = Infinity;
let dangerCount = 0;
for (const col of colliders) {
// AABB vs sphere collision
const dx = Math.max(col.halfSize.x, Math.abs(playerPos.x - col.center.x)) - col.halfSize.x;
const dy = Math.max(col.halfSize.y, Math.abs(playerPos.y - col.center.y)) - col.halfSize.y;
const dz = Math.max(col.halfSize.z, Math.abs(playerPos.z - col.center.z)) - col.halfSize.z;
const distSq = Math.max(0, dx) * Math.max(0, dx) +
Math.max(0, dy) * Math.max(0, dy) +
Math.max(0, dz) * Math.max(0, dz);
const dist = Math.sqrt(distSq);
minDist = Math.min(minDist, dist);
if (dist < playerRadius) {
collided = true;
}
if (dist < playerRadius + (col.dangerZone || 6) && dist >= playerRadius) {
nearMiss = true;
if (dist < playerRadius + (col.dangerZone || 6) - 2) {
dangerCount++;
}
}
}
return { collided, nearMiss, minDist, dangerZones: dangerCount };
}
};
// ── module: src/game.ts ──
__mods["src/game.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGameState = createGameState;
exports.createGameMetrics = createGameMetrics;
exports.calculateScoring = calculateScoring;
exports.getDifficulty = getDifficulty;
exports.resetGame = resetGame;
function createGameState() {
return {
running: false,
paused: false,
gameOver: false,
started: false,
};
}
function createGameMetrics() {
return {
score: 0,
multiplier: 1,
maxMultiplier: 1,
maxSpeed: 0,
distance: 0,
timeAlive: 0,
};
}
function calculateScoring(metrics, player, dt, nearMiss, nearMissDist, dangerCount) {
let scoreGained = 0;
let newMultiplier = metrics.multiplier;
let nearMissTriggered = false;
// Base distance score
const distanceScore = player.speed * dt * 0.1;
scoreGained += distanceScore;
// Near-miss bonus
if (nearMiss && player.nearMissCooldown <= 0 && player.position.z - nearMissDist < player.position.z - 30) {
// Calculate bonus based on proximity
const proximityBonus = Math.max(1, 5 - nearMissDist * 0.3);
scoreGained += proximityBonus * 10;
// Increase multiplier
newMultiplier = Math.min(10, newMultiplier + 0.2 + dangerCount * 0.1);
nearMissTriggered = true;
player.lastNearMissTime = player.position.z;
player.nearMissCooldown = player.nearMissCooldown;
}
// Speed bonus
if (player.speed > 100) {
scoreGained += (player.speed - 100) * dt * 0.05;
}
// Turbo bonus
if (player.turboActive) {
scoreGained += 30 * dt;
newMultiplier = Math.min(10, newMultiplier + dt * 0.5);
}
// Apply multiplier decay when not getting near misses
const timeSinceNearMiss = (player.position.z - player.lastNearMissTime) / player.speed;
if (timeSinceNearMiss > 2) {
newMultiplier = Math.max(1, newMultiplier - dt * 0.3);
}
metrics.score += scoreGained * newMultiplier;
metrics.multiplier = newMultiplier;
metrics.maxMultiplier = Math.max(metrics.maxMultiplier, newMultiplier);
metrics.maxSpeed = Math.max(metrics.maxSpeed, player.speed);
metrics.distance = player.position.z;
metrics.timeAlive += dt;
return {
scoreGained,
newMultiplier,
nearMissTriggered,
};
}
function getDifficulty(metrics) {
return 1 + metrics.timeAlive * 0.02 + metrics.score * 0.00001;
}
function resetGame(metrics, player) {
metrics.score = 0;
metrics.multiplier = 1;
metrics.maxMultiplier = 1;
metrics.maxSpeed = 0;
metrics.distance = 0;
metrics.timeAlive = 0;
player.position = { x: 0, y: 0, z: 0 };
player.velocity = { x: 0, y: 0, z: 100 };
player.rotation = { x: 0, y: 0, z: 0 };
player.targetRotation = { x: 0, y: 0, z: 0 };
player.energy = 100;
player.speed = 80;
player.baseSpeed = 80;
player.turboCharge = 0;
player.turboActive = false;
player.turboDuration = 0;
player.lastNearMissTime = 0;
}
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initRenderer = initRenderer;
exports.updatePlayerMesh = updatePlayerMesh;
exports.spawnDebris = spawnDebris;
exports.createChunkMesh = createChunkMesh;
exports.createChunkCollectibleMeshes = createChunkCollectibleMeshes;
exports.removeChunkMesh = removeChunkMesh;
exports.removeCollectibleMesh = removeCollectibleMesh;
exports.updateCamera = updateCamera;
exports.updateCameraShake = updateCameraShake;
exports.updateCollectibles = updateCollectibles;
exports.render = render;
exports.onResize = onResize;
exports.cleanupRenderer = cleanupRenderer;
const types_1 = require("./types");
let renderer;
let camera;
let scene;
let composer;
let playerMesh;
let chunksMeshMap = new Map();
let collectibleMeshMap = new Map();
let trailSystem = null;
let debrisSystem = null;
let sunMesh = null;
let particles = [];
let debrisParticles = [];
let lastTime = 0;
// Reusable buffers for trail
const trailPositions = new Float32Array(1000 * 3);
const trailColors = new Float32Array(1000 * 4);
const trailSizes = new Float32Array(1000);
let trailCount = 0;
let trailHead = 0;
function initRenderer(canvasId) {
const container = document.getElementById(canvasId);
if (!container)
throw new Error('Canvas container not found');
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: false,
powerPreference: 'high-performance',
});
renderer.setClearColor(types_1.PALETTE.skyTop, 1);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.autoClear = false;
container.appendChild(renderer.domElement);
// Camera
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.5, 2000);
camera.position.set(0, 2, -8);
camera.up.set(0, 1, 0);
// Scene
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(new THREE.Color(types_1.PALETTE.skyTop), 0.0018);
// Lighting
const ambient = new THREE.AmbientLight(0x442255, 0.5);
scene.add(ambient);
const hemi = new THREE.HemisphereLight(0xff8844, 0x221144, 0.8);
hemi.position.set(0, 50, 50);
scene.add(hemi);
const sunLight = new THREE.DirectionalLight(0xffaa44, 1.5);
sunLight.position.set(-30, 40, 200);
scene.add(sunLight);
// Create sun visual
const sunGeo = new THREE.SphereGeometry(25, 16, 16);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xffcc44 });
sunMesh = new THREE.Mesh(sunGeo, sunMat);
sunMesh.position.set(0, 30, 500);
scene.add(sunMesh);
// Create player craft
playerMesh = createPlayerCraft();
scene.add(playerMesh);
// Create trail particle system
createTrailSystem();
// Create debris system
createDebrisSystem();
// Initialize composer
initComposer();
window.addEventListener('resize', onResize);
lastTime = performance.now();
}
function initComposer() {
// Custom composer implementation using raw WebGL2
// We'll implement bloom and afterimage via custom render passes
const gl = renderer;
composer = new CustomComposer(camera, renderer);
}
function createPlayerCraft() {
const group = new THREE.Group();
// Main body - sleek wedge shape
const bodyGeo = new THREE.ConeGeometry(0.6, 2.5, 8);
bodyGeo.rotateX(Math.PI / 2);
const bodyMat = new THREE.MeshStandardMaterial({
color: 0x888899,
metalness: 0.9,
roughness: 0.2,
flatShading: true,
});
const body = new THREE.Mesh(bodyGeo, bodyMat);
group.add(body);
// Cockpit
const cockpitGeo = new THREE.ConeGeometry(0.3, 1.0, 6);
cockpitGeo.rotateX(Math.PI / 2);
const cockpitMat = new THREE.MeshStandardMaterial({
color: 0x44ddff,
metalness: 0.3,
roughness: 0.1,
emissive: 0x2288aa,
emissiveIntensity: 0.3,
transparent: true,
opacity: 0.8,
});
const cockpit = new THREE.Mesh(cockpitGeo, cockpitMat);
cockpit.position.z = 0.3;
cockpit.position.y = 0.15;
group.add(cockpit);
// Wings
const wingGeo = new THREE.BoxGeometry(2.8, 0.08, 1.0);
const wingMat = new THREE.MeshStandardMaterial({
color: 0x666677,
metalness: 0.8,
roughness: 0.3,
flatShading: true,
});
const wing = new THREE.Mesh(wingGeo, wingMat);
wing.position.z = -0.2;
group.add(wing);
// Engine glow
const engineGeo = new THREE.ConeGeometry(0.35, 1.2, 8);
engineGeo.rotateX(-Math.PI / 2);
const engineMat = new THREE.MeshBasicMaterial({
color: types_1.PALETTE.accentOrange,
transparent: true,
opacity: 0.9,
});
const engine = new THREE.Mesh(engineGeo, engineMat);
engine.position.z = 1.8;
group.add(engine);
// Engine tip glow
const tipGeo = new THREE.SphereGeometry(0.2, 8, 8);
const tipMat = new THREE.MeshBasicMaterial({
color: 0xffffff,
transparent: true,
opacity: 0.8,
});
const tip = new THREE.Mesh(tipGeo, tipMat);
tip.position.z = 2.3;
group.add(tip);
return group;
}
function createTrailSystem() {
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(trailPositions, 3));
geo.setAttribute('color', new THREE.BufferAttribute(trailColors, 4));
geo.setAttribute('size', new THREE.BufferAttribute(trailSizes, 1));
const mat = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
},
vertexShader: `
attribute float size;
attribute vec4 color;
varying vec4 vColor;
varying float vSize;
uniform float time;
void main() {
vColor = color;
vSize = size;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (300.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying vec4 vColor;
void main() {
float dist = length(gl_PointCoord - 0.5);
if (dist > 0.5) discard;
float alpha = smoothstep(0.5, 0.1, dist) * vColor.a;
gl_FragColor = vec4(vColor.rgb, alpha);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
trailSystem = new THREE.Points(geo, mat);
scene.add(trailSystem);
}
function createDebrisSystem() {
const geo = new THREE.BufferGeometry();
const positions = new Float32Array(500 * 3);
const colors = new Float32Array(500 * 3);
const sizes = new Float32Array(500);
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geo.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const mat = new THREE.ShaderMaterial({
vertexShader: `
attribute float size;
attribute vec3 color;
varying vec3 vColor;
void main() {
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (200.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
float dist = length(gl_PointCoord - 0.5);
if (dist > 0.5) discard;
float alpha = smoothstep(0.5, 0.0, dist) * 0.6;
gl_FragColor = vec4(vColor, alpha);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
});
debrisSystem = new THREE.Points(geo, mat);
scene.add(debrisSystem);
}
function updatePlayerMesh(player) {
if (!playerMesh)
return;
playerMesh.position.set(player.position.x, player.position.y, player.position.z);
playerMesh.rotation.set(player.rotation.x, player.rotation.y, player.rotation.z);
// Engine glow intensity based on speed
const engine = playerMesh.children[3];
const tip = playerMesh.children[4];
const glowIntensity = 0.6 + (player.speed / player.maxSpeed) * 0.5;
if (engine && engine.material) {
engine.material.opacity = player.turboActive ? 1.0 : glowIntensity;
engine.material.color = player.turboActive
? new THREE.Color(types_1.PALETTE.turboColor)
: new THREE.Color(types_1.PALETTE.accentOrange);
}
// Add trail particles
addTrailParticles(player);
}
function addTrailParticles(player) {
const count = player.turboActive ? 5 : 3;
for (let i = 0; i < count; i++) {
const idx = trailHead % 1000;
const px = player.position.x + (Math.random() - 0.5) * 0.5;
const py = player.position.y + (Math.random() - 0.5) * 0.3;
const pz = player.position.z + 2 + Math.random() * 2;
trailPositions[idx * 3] = px;
trailPositions[idx * 3 + 1] = py;
trailPositions[idx * 3 + 2] = pz;
const r = player.turboActive ? 1.0 : 1.0;
const g = player.turboActive ? 0.8 : 0.4;
const b = player.turboActive ? 0.2 : 0.1;
trailColors[idx * 4] = r;
trailColors[idx * 4 + 1] = g;
trailColors[idx * 4 + 2] = b;
trailColors[idx * 4 + 3] = 0.7;
trailSizes[idx] = player.turboActive ? 1.5 : 1.0;
trailHead++;
trailCount = Math.min(trailCount + 1, 1000);
}
if (trailSystem && trailSystem.geometry) {
trailSystem.geometry.attributes.position.needsUpdate = true;
trailSystem.geometry.attributes.color.needsUpdate = true;
trailSystem.geometry.attributes.size.needsUpdate = true;
}
}
function spawnDebris(player, count) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 20 + Math.random() * 30;
debrisParticles.push({
position: {
x: player.position.x + Math.cos(angle) * 3,
y: player.position.y + (Math.random() - 0.5) * 3,
z: player.position.z + 5 + Math.random() * 5,
},
velocity: {
x: Math.cos(angle) * speed,
y: (Math.random() - 0.5) * speed * 0.5,
z: Math.random() * speed * 0.5,
},
life: 1,
maxLife: 1 + Math.random(),
size: 0.3 + Math.random() * 0.5,
color: { r: 1, g: 0.4 + Math.random() * 0.3, b: 0.1 },
});
}
}
function createChunkMesh(chunk) {
const group = new THREE.Group();
// Walls
for (const wall of chunk.walls) {
const wallGroup = createWallMesh(wall);
group.add(wallGroup);
}
// Obstacles
for (const obs of chunk.obstacles) {
const mesh = createObstacleMesh(obs);
group.add(mesh);
}
chunksMeshMap.set(chunk.id, group);
scene.add(group);
return group;
}
function createWallMesh(wall) {
const group = new THREE.Group();
const segs = Math.floor(wall.size.z / 20);
const segWidth = wall.size.x / Math.max(1, Math.floor(wall.size.x / 8));
const segHeight = wall.size.y / Math.max(1, Math.floor(wall.size.y / 8));
const colors = [types_1.PALETTE.wallColor1, types_1.PALETTE.wallColor2, types_1.PALETTE.wallColor3];
for (let i = 0; i < Math.min(segs * 3, 30); i++) {
const h = wall.size.x + Math.random() * wall.size.x * 0.5;
const w = segWidth * (1 + Math.random());
const d = 5 + Math.random() * 8;
const geo = new THREE.BoxGeometry(w, h, d);
const mat = new THREE.MeshStandardMaterial({
color: colors[Math.floor(Math.random() * colors.length)],
flatShading: true,
roughness: 0.9,
metalness: 0.1,
});
const mesh = new THREE.Mesh(geo, mat);
const zOff = (i / (segs * 3)) * wall.size.z - wall.size.z / 2;
const yOff = (Math.random() - 0.5) * wall.size.y * 0.4;
mesh.position.set(wall.position.x + (Math.random() - 0.5) * 5, wall.position.y + yOff + h * 0.3, wall.position.z + zOff);
mesh.rotation.z = (Math.random() - 0.5) * 0.2;
mesh.rotation.x = (Math.random() - 0.5) * 0.1;
group.add(mesh);
}
return group;
}
function createObstacleMesh(obs) {
let geo;
if (obs.type === 'monolith') {
geo = new THREE.CylinderGeometry(obs.size.x * 0.3, obs.size.x * 0.5, obs.size.y, 6);
}
else {
geo = new THREE.OctahedronGeometry(Math.max(obs.size.x, obs.size.y, obs.size.z) * 0.4);
}
const mat = new THREE.MeshStandardMaterial({
color: Math.random() > 0.5 ? types_1.PALETTE.accentPurple : types_1.PALETTE.accentCyan,
flatShading: true,
roughness: 0.4,
metalness: 0.7,
emissive: Math.random() > 0.5 ? 0x442266 : 0x224455,
emissiveIntensity: 0.3,
});
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(obs.position.x, obs.position.y, obs.position.z);
return mesh;
}
function createChunkCollectibleMeshes(chunk) {
for (const col of chunk.collectibles) {
if (col.collected)
continue;
const group = new THREE.Group();
if (col.type === 'energy') {
const geo = new THREE.IcosahedronGeometry(0.8, 0);
const mat = new THREE.MeshStandardMaterial({
color: types_1.PALETTE.energyColor,
emissive: 0x22aa66,
emissiveIntensity: 1.0,
flatShading: true,
});
const mesh = new THREE.Mesh(geo, mat);
group.add(mesh);
}
else {
const geo = new THREE.OctahedronGeometry(0.9, 0);
const mat = new THREE.MeshStandardMaterial({
color: types_1.PALETTE.turboColor,
emissive: 0xaa8800,
emissiveIntensity: 1.0,
flatShading: true,
});
const mesh = new THREE.Mesh(geo, mat);
group.add(mesh);
}
// Glow ring
const ringGeo = new THREE.CylinderGeometry(1.3, 1.3, 0.05, 12);
ringGeo.rotateX(Math.PI / 2);
const ringMat = new THREE.MeshBasicMaterial({
color: col.type === 'energy' ? 0x44ffaa : types_1.PALETTE.turboColor,
transparent: true,
opacity: 0.4,
});
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.userData.isRing = true;
group.add(ring);
group.position.set(col.position.x, col.position.y, col.position.z);
scene.add(group);
collectibleMeshMap.set(col.position.x * 1000 + col.position.y * 100 + col.position.z, group);
}
}
function removeChunkMesh(chunkId) {
const group = chunksMeshMap.get(chunkId);
if (group) {
group.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.geometry.dispose();
if (obj.material.dispose)
obj.material.dispose();
}
});
scene.remove(group);
chunksMeshMap.delete(chunkId);
}
}
function removeCollectibleMesh(col) {
const key = col.position.x * 1000 + col.position.y * 100 + col.position.z;
const group = collectibleMeshMap.get(key);
if (group) {
group.traverse((obj) => {
if (obj instanceof THREE.Mesh) {
obj.geometry.dispose();
if (obj.material.dispose)
obj.material.dispose();
}
});
scene.remove(group);
collectibleMeshMap.delete(key);
}
}
function updateCamera(player, cameraState) {
// Camera follows player with slight offset and lag
const targetX = player.position.x * 0.6;
const targetY = player.position.y * 0.4 + 2;
const targetZ = player.position.z - (player.turboActive ? 10 : 8);
camera.position.x += (targetX - camera.position.x) * 5 * 0.016;
camera.position.y += (targetY - camera.position.y) * 5 * 0.016;
camera.position.z += (targetZ - camera.position.z) * 5 * 0.016;
// Apply shake
camera.position.x += cameraState.shakeVector.x;
camera.position.y += cameraState.shakeVector.y;
// Look slightly ahead
const lookAhead = player.turboActive ? 60 : 40;
camera.lookAt(new THREE.Vector3(player.position.x * 0.3, player.position.y * 0.2 + 1, player.position.z + lookAhead));
// FOV changes based on speed and turbo
cameraState.targetFov = 70 + (player.speed / player.maxSpeed) * 10;
if (player.turboActive) {
cameraState.targetFov = Math.min(95, cameraState.targetFov + 10);
}
camera.fov += (cameraState.targetFov - camera.fov) * 3 * 0.016;
camera.updateProjectionMatrix();
}
function updateCameraShake(cameraState, intensity) {
if (intensity > 0) {
cameraState.shakeVector.x = (Math.random() - 0.5) * intensity;
cameraState.shakeVector.y = (Math.random() - 0.5) * intensity;
cameraState.shakeVector.z = (Math.random() - 0.5) * intensity * 0.5;
}
else {
cameraState.shakeVector.x *= 0.8;
cameraState.shakeVector.y *= 0.8;
cameraState.shakeVector.z *= 0.8;
}
}
function updateCollectibles(dt) {
collectibleMeshMap.forEach((group) => {
group.rotation.y += dt * 2;
group.rotation.x += dt * 0.5;
const ring = group.children.find((c) => c.userData && c.userData.isRing);
if (ring) {
ring.rotation.z -= dt * 1.5;
}
});
}
function render() {
renderer.render(scene, camera);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
if (composer) {
composer.setSize(window.innerWidth, window.innerHeight);
}
}
function cleanupRenderer() {
window.removeEventListener('resize', onResize);
if (renderer) {
renderer.dispose();
const container = document.getElementById('game-canvas');
if (container && renderer.domElement) {
container.removeChild(renderer.domElement);
}
}
chunksMeshMap.clear();
collectibleMeshMap.clear();
}
// Custom composer class for bloom
class CustomComposer {
constructor(camera, renderer) {
this.camera = camera;
this.renderer = renderer;
this.bloomStrength = 0.8;
this.width = window.innerWidth;
this.height = window.innerHeight;
this.renderTarget1 = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
});
this.renderTarget2 = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
});
}
setSize(width, height) {
this.width = width;
this.height = height;
this.renderTarget1.setSize(width, height);
this.renderTarget2.setSize(width, height);
}
render() {
// For now, just render directly - the glow from emissive materials provides enough effect
this.renderer.render(scene, this.camera);
}
}
};
// ── types: globals.d.ts ──
__mods["globals.d.ts"] = function (exports) {};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>