Canyon Run
Qwen 3.6 27B Fable Fusion 711 Uncensored Heretic NM DAU NEO MAX MTP GGUF · full
◈Preview unavailable — try playing the result
Browser check: Preview unavailable or timed out. This is a technical check, not a quality review.
Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 46,709 bytes · SHA-256 77bf4569b576
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canyon Rush - High-Speed Procedural Canyon Racer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
overflow: hidden;
background: #0a0012;
font-family: 'Courier New', monospace;
cursor: none;
}
#gameCanvas {
display: block;
width: 100vw;
height: 100vh;
}
/* HUD */
#hud {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
pointer-events: none;
z-index: 10;
}
/* Speed bar */
#speedBarContainer {
position: absolute;
right: 40px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 200px;
background: rgba(255,255,255,0.1);
border-radius: 2px;
}
#speedBar {
position: absolute;
bottom: 0;
width: 100%;
height: 0%;
background: linear-gradient(to top, #ff6b35, #ff9500, #ffcc00);
border-radius: 2px;
transition: height 0.1s ease;
box-shadow: 0 0 10px rgba(255,149,0,0.8);
}
#speedText {
position: absolute;
right: 55px;
top: 50%;
transform: translateY(-50%);
color: #ff9500;
font-size: 14px;
text-align: right;
text-shadow: 0 0 8px rgba(255,149,0,0.8);
}
/* Score & Multiplier */
#scoreContainer {
position: absolute;
top: 40px;
left: 50%;
transform: translateX(-50%);
text-align: center;
}
#scoreText {
color: #fff;
font-size: 28px;
text-shadow: 0 0 15px rgba(255,255,255,0.8);
}
#multiplierText {
color: #ff6b35;
font-size: 36px;
text-shadow: 0 0 20px rgba(255,107,53,1);
transition: transform 0.1s, opacity 0.2s;
}
/* Energy bar */
#energyBarContainer {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 6px;
background: rgba(255,255,255,0.1);
border-radius: 3px;
overflow: hidden;
}
#energyBar {
width: 100%;
height: 100%;
background: linear-gradient(to right, #00e5ff, #64ffda);
border-radius: 3px;
box-shadow: 0 0 10px rgba(0,229,255,0.6);
transition: width 0.1s ease, box-shadow 0.3s;
}
#energyBar.critical {
background: linear-gradient(to right, #ff1744, #ff9100);
box-shadow: 0 0 15px rgba(255,23,68,0.9);
}
/* Boost meter */
#boostBarContainer {
position: absolute;
left: 40px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 150px;
background: rgba(255,255,255,0.1);
border-radius: 2px;
}
#boostBar {
position: absolute;
bottom: 0;
width: 100%;
height: 0%;
background: linear-gradient(to top, #ff00ff, #aa00ff, #7c4dff);
border-radius: 2px;
transition: height 0.2s ease;
box-shadow: 0 0 10px rgba(186,48,255,0.8);
}
#boostReadyText {
position: absolute;
left: 55px;
top: 50%;
transform: translateY(-50%);
color: #e040fb;
font-size: 11px;
text-shadow: 0 0 10px rgba(224,64,251,1);
opacity: 0;
transition: opacity 0.3s;
}
/* Game Over */
#gameOver {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: radial-gradient(ellipse at center, rgba(10,0,18,0.7) 0%, rgba(10,0,18,0.95) 100%);
display: none;
justify-content: center;
align-items: center;
flex-direction: column;
pointer-events: all;
z-index: 100;
cursor: default;
}
#gameOver h1 {
color: #ff6b35;
font-size: 60px;
text-shadow: 0 0 40px rgba(255,107,53,1);
margin-bottom: 30px;
letter-spacing: 8px;
}
.stat-row {
color: #fff;
font-size: 22px;
margin: 10px 0;
text-shadow: 0 0 10px rgba(255,255,255,0.6);
}
.stat-label { color: #64ffda; margin-right: 15px; }
.stat-value { color: #ffcc00; }
#restartBtn {
margin-top: 40px;
padding: 16px 48px;
background: transparent;
border: 2px solid #ff6b35;
color: #ff6b35;
font-size: 18px;
font-family: 'Courier New', monospace;
cursor: pointer;
letter-spacing: 4px;
transition: all 0.3s;
pointer-events: all;
text-transform: uppercase;
}
#restartBtn:hover {
background: #ff6b35;
color: #0a0012;
box-shadow: 0 0 30px rgba(255,107,53,0.8);
}
/* Start Screen */
#startScreen {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: radial-gradient(ellipse at center, rgba(40,10,40,0.95) 0%, rgba(10,0,18,0.98) 100%);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
pointer-events: all;
z-index: 100;
cursor: default;
}
#startScreen h1 {
color: #ff9500;
font-size: 52px;
text-shadow: 0 0 50px rgba(255,149,0,1);
margin-bottom: 10px;
letter-spacing: 10px;
}
#startScreen .subtitle {
color: #64ffda;
font-size: 16px;
margin-bottom: 40px;
letter-spacing: 4px;
text-shadow: 0 0 10px rgba(100,255,218,0.6);
}
#startBtn {
padding: 16px 48px;
background: transparent;
border: 2px solid #ff9500;
color: #ff9500;
font-size: 18px;
font-family: 'Courier New', monospace;
cursor: pointer;
letter-spacing: 6px;
transition: all 0.3s;
text-transform: uppercase;
}
#startBtn:hover {
background: #ff9500;
color: #0a0012;
box-shadow: 0 0 30px rgba(255,149,0,0.8);
}
.controls-info {
margin-top: 40px;
color: rgba(255,255,255,0.5);
font-size: 12px;
text-align: center;
line-height: 1.8;
}
.controls-info span { color: #ff9500; }
/* Near miss flash */
#nearMissFlash {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: radial-gradient(circle, rgba(255,107,53,0.3), transparent 60%);
opacity: 0;
pointer-events: none;
z-index: 5;
transition: opacity 0.15s;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="hud">
<div id="scoreContainer">
<div id="scoreText">0</div>
<div id="multiplierText">x1.0</div>
</div>
<div id="speedBarContainer"><div id="speedBar"></div></div>
<div id="speedText">0 km/h</div>
<div id="boostBarContainer"><div id="boostBar"></div></div>
<div id="boostReadyText">BOOST READY</div>
<div id="energyBarContainer"><div id="energyBar"></div></div>
</div>
<div id="nearMissFlash"></div>
<div id="startScreen">
<h1>CANYON RUSH</h1>
<div class="subtitle">HIGH-SPEED PROCEDURAL CANYON RACER</div>
<button id="startBtn">START RUN</button>
<div class="controls-info">
<span>MOUSE</span> — steer & bank<br>
<span>CLICK / SHIFT</span> — boost<br>
<span>F</span> — force boost when ready
</div>
</div>
<div id="gameOver">
<h1>RUN ENDED</h1>
<div class="stat-row"><span class="stat-label">DISTANCE</span><span class="stat-value" id="finalScore">0</span> m</div>
<div class="stat-row"><span class="stat-label">MAX SPEED</span><span class="stat-value" id="finalSpeed">0</span> km/h</div>
<div class="stat-row"><span class="stat-label">BEST MULT</span><span class="stat-value" id="finalMult">x1.0</span></div>
<div class="stat-row"><span class="stat-label">NEAR MISSES</span><span class="stat-value" id="finalNearMisses">0</span></div>
<button id="restartBtn">RUN AGAIN</button>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
// ============================================================
// AUDIO SYSTEM (Web Audio API)
// ============================================================
class AudioSys {
constructor() {
this.ctx = null;
this.engineOsc = null;
this.engineGain = null;
this.windNode = null;
this.windGain = null;
this.initialized = false;
}
init() {
if (this.initialized) return;
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.initialized = true;
// Engine hum
this.engineOsc = this.ctx.createOscillator();
this.engineOsc.type = 'sawtooth';
this.engineOsc.frequency.value = 60;
this.engineGain = this.ctx.createGain();
this.engineGain.gain.value = 0.03;
const engineFilter = this.ctx.createBiquadFilter();
engineFilter.type = 'lowpass';
engineFilter.frequency.value = 200;
this.engineFilter = engineFilter;
this.engineOsc.connect(this.engineFilter);
this.engineFilter.connect(this.engineGain);
this.engineGain.connect(this.ctx.destination);
this.engineOsc.start();
// Wind noise
const bufferSize = 2 * this.ctx.sampleRate;
const noiseBuffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const output = noiseBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) output[i] = Math.random() * 2 - 1;
this.windNode = this.ctx.createBufferSource();
this.windNode.buffer = noiseBuffer;
this.windNode.loop = true;
this.windGain = this.ctx.createGain();
this.windGain.gain.value = 0.02;
const windFilter = this.ctx.createBiquadFilter();
windFilter.type = 'bandpass';
windFilter.frequency.value = 400;
windFilter.Q.value = 0.5;
this.windNode.connect(windFilter);
windFilter.connect(this.windGain);
this.windGain.connect(this.ctx.destination);
this.windNode.start();
}
updateEngine(speedRatio) {
if (!this.initialized) return;
const freq = 50 + speedRatio * 120;
this.engineOsc.frequency.setTargetAtTime(freq, this.ctx.currentTime, 0.1);
this.engineFilter.frequency.setTargetAtTime(180 + speedRatio * 300, this.ctx.currentTime, 0.1);
this.engineGain.gain.setTargetAtTime(0.025 + speedRatio * 0.02, this.ctx.currentTime, 0.1);
}
updateWind(speedRatio) {
if (!this.initialized) return;
this.windGain.gain.setTargetAtTime(0.015 + speedRatio * 0.04, this.ctx.currentTime, 0.1);
}
playCollect() {
if (!this.initialized) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(880, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1320, this.ctx.currentTime + 0.08);
gain.gain.setValueAtTime(0.12, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.2);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.2);
}
playNearMiss() {
if (!this.initialized) return;
// Whoosh
const bufferSize = this.ctx.sampleRate * 0.3;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
const t = i / bufferSize;
data[i] = (Math.random() * 2 - 1) * Math.exp(-t * 6) * Math.sin(t * Math.PI * 20);
}
const source = this.ctx.createBufferSource();
source.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 2000;
const gain = this.ctx.createGain();
gain.gain.value = 0.15;
source.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
source.start();
}
playBoost() {
if (!this.initialized) return;
const osc = this.ctx.createOscillator();
const gain = this.ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(120, this.ctx.currentTime);
osc.frequency.exponentialRampToValueAtTime(400, this.ctx.currentTime + 0.3);
gain.gain.setValueAtTime(0.08, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + 0.5);
osc.connect(gain);
gain.connect(this.ctx.destination);
osc.start();
osc.stop(this.ctx.currentTime + 0.5);
}
playCrash() {
if (!this.initialized) return;
const bufferSize = this.ctx.sampleRate * 0.6;
const buffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {
const t = i / bufferSize;
data[i] = (Math.random() * 2 - 1) * Math.exp(-t * 3);
}
const source = this.ctx.createBufferSource();
source.buffer = buffer;
const filter = this.ctx.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 800;
const gain = this.ctx.createGain();
gain.gain.value = 0.3;
source.connect(filter);
filter.connect(gain);
gain.connect(this.ctx.destination);
source.start();
this.engineOsc.stop();
this.windNode.stop();
}
}
// ============================================================
// GAME STATE
// ============================================================
const STATE = {
STARTED: 0,
PLAYING: 1,
GAMEOVER: 2
};
// ============================================================
// CONSTANTS
// ============================================================
const CHUNK_LENGTH = 100;
const CHUNK_COUNT = 12;
const CANON_HALF_WIDTH_BASE = 40;
const CANON_HEIGHT_BASE = 35;
const PLAYER_SPEED_BASE = 180;
const PLAYER_SPEED_MAX = 500;
const ENERGY_DECAY_RATE = 3.5;
const BOOST_CONSUME_RATE = 45;
const BOOST_MAX = 100;
const BOOST_SPEED_MULT = 2.0;
const BOOST_DURATION = 2.0;
const NEAR_MISS_THRESHOLD = 6;
const NEAR_MISS_BONUS_THRESHOLD = 4;
const ORB_CHARGE = 18;
// ============================================================
// THREE.JS SETUP
// ============================================================
const canvas = document.getElementById('gameCanvas');
const renderer = new THREE.WebGLRenderer({ canvas, antialias: false, powerPreference: 'high-performance' });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a0525);
scene.fog = new THREE.FogExp2(0x1a0525, 0.007);
const camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.5, 600);
camera.position.set(0, 8, -20);
// Post-processing
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.2, 0.4, 0.1
);
composer.addPass(bloomPass);
// Lighting
const ambientLight = new THREE.AmbientLight(0x402040, 0.6);
scene.add(ambientLight);
const sunLight = new THREE.DirectionalLight(0xff8844, 1.8);
sunLight.position.set(30, 60, -80);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 1024;
sunLight.shadow.mapSize.height = 1024;
sunLight.shadow.camera.near = 1;
sunLight.shadow.camera.far = 300;
sunLight.shadow.camera.left = -60;
sunLight.shadow.camera.right = 60;
sunLight.shadow.camera.top = 60;
sunLight.shadow.camera.bottom = -60;
scene.add(sunLight);
const rimLight = new THREE.DirectionalLight(0x6600aa, 0.7);
rimLight.position.set(-20, 30, 50);
scene.add(rimLight);
// ============================================================
// MATERIALS (flat shading, no textures)
// ============================================================
const matCanyonRock = new THREE.MeshStandardMaterial({
color: 0x3d1a2e,
flatShading: true,
roughness: 0.9,
metalness: 0.1
});
const matCanyonRockWarm = new THREE.MeshStandardMaterial({
color: 0x6b2e4a,
flatShading: true,
roughness: 0.85,
metalness: 0.15
});
const matMonolith = new THREE.MeshStandardMaterial({
color: 0x222233,
flatShading: true,
roughness: 0.3,
metalness: 0.8,
emissive: 0x111133,
emissiveIntensity: 0.3
});
const matPlayer = new THREE.MeshStandardMaterial({
color: 0xcccccc,
flatShading: true,
roughness: 0.2,
metalness: 0.9,
emissive: 0x00e5ff,
emissiveIntensity: 0.4
});
const matPlayerAccent = new THREE.MeshStandardMaterial({
color: 0xff6b35,
flatShading: true,
roughness: 0.3,
metalness: 0.7,
emissive: 0xff6b35,
emissiveIntensity: 0.6
});
const matOrb = new THREE.MeshStandardMaterial({
color: 0x00ffcc,
flatShading: true,
emissive: 0x00ffcc,
emissiveIntensity: 1.5,
transparent: true,
opacity: 0.9
});
const matGround = new THREE.MeshStandardMaterial({
color: 0x2a1520,
flatShading: true,
roughness: 1.0,
metalness: 0.0
});
const matDebris = new THREE.MeshBasicMaterial({ color: 0xff8844 });
// ============================================================
// REUSABLE GEOMETRIES
// ============================================================
const geoMonolith = new THREE.CylinderGeometry(1.2, 1.2, 12, 6);
const geoOrb = new THREE.OctahedronGeometry(0.8, 0);
const geoDebris = new THREE.TetrahedronGeometry(0.15, 0);
// ============================================================
// PLAYER CRAFT
// ============================================================
class Player {
constructor() {
this.mesh = new THREE.Group();
this.speed = PLAYER_SPEED_BASE;
this.maxSpeed = PLAYER_SPEED_MAX;
this.targetX = 0;
this.targetY = 8;
this.currentX = 0;
this.currentY = 8;
this.roll = 0;
this.pitch = 0;
this.energy = 100;
this.boost = 0;
this.isBoosting = false;
this.boostTimer = 0;
this.maxSpeedReached = 0;
this.distance = 0;
this.createModel();
scene.add(this.mesh);
}
createModel() {
// Main fuselage - sleek tetrahedral shape
const fuselage = new THREE.Mesh(
new THREE.ConeGeometry(0.8, 4, 4),
matPlayer
);
fuselage.rotation.x = Math.PI / 2;
fuselage.castShadow = true;
this.mesh.add(fuselage);
// Nose accent
const nose = new THREE.Mesh(
new THREE.ConeGeometry(0.3, 1.5, 4),
matPlayerAccent
);
nose.rotation.x = Math.PI / 2;
nose.position.z = -2.75;
nose.castShadow = true;
this.mesh.add(nose);
// Wings
const wingGeo = new THREE.BufferGeometry();
const wingVerts = new Float32Array([
0, 0, -0.5, 2.5, -0.5, 0, 0, 0, 1.5,
0, 0, -0.5, 0, 0, 1.5, -2.5, -0.5, 0,
0, 0, -0.5, -2.5, -0.5, 0, 0, 0, 1.5
]);
wingGeo.setAttribute('position', new THREE.BufferAttribute(wingVerts, 3));
wingGeo.computeVertexNormals();
const wings = new THREE.Mesh(wingGeo, matPlayer);
wings.castShadow = true;
this.mesh.add(wings);
// Wing tips
const wingTipGeo = new THREE.ConeGeometry(0.15, 0.6, 4);
const wingTipL = new THREE.Mesh(wingTipGeo, matPlayerAccent);
wingTipL.position.set(2.5, -0.5, -0.2);
wingTipL.rotation.z = -Math.PI / 2;
this.mesh.add(wingTipL);
const wingTipR = new THREE.Mesh(wingTipGeo, matPlayerAccent);
wingTipR.position.set(-2.5, -0.5, -0.2);
wingTipR.rotation.z = Math.PI / 2;
this.mesh.add(wingTipR);
// Engine glow
const engineGlow = new THREE.Mesh(
new THREE.SphereGeometry(0.4, 6, 6),
new THREE.MeshBasicMaterial({ color: 0x00e5ff })
);
engineGlow.position.z = 2.2;
this.engineGlow = engineGlow;
this.mesh.add(engineGlow);
// Engine light
const engineLight = new THREE.PointLight(0x00e5ff, 1.5, 15);
engineLight.position.z = 2.2;
this.mesh.add(engineLight);
this.engineLight = engineLight;
}
update(dt, mouseX, mouseY) {
// Movement interpolation (weighty feel)
const moveSpeed = this.isBoosting ? 22 : 16;
this.currentX += (mouseX - this.currentX) * moveSpeed * dt;
this.currentY += (mouseY - this.currentY) * moveSpeed * dt;
// Banking / rolling based on lateral movement
const targetRoll = (mouseX - this.currentX) * -1.2;
this.roll += (targetRoll - this.roll) * 12 * dt;
// Pitch based on vertical movement
const targetPitch = (mouseY - this.currentY) * 0.8;
this.pitch += (targetPitch - this.pitch) * 12 * dt;
this.mesh.position.x = this.currentX;
this.mesh.position.y = this.currentY;
this.mesh.rotation.z = this.roll;
this.mesh.rotation.x = this.pitch;
// Engine effects
const boostScale = this.isBoosting ? 2.5 : 1.0;
this.engineGlow.scale.setScalar(boostScale);
this.engineLight.intensity = 1.5 + (this.isBoosting ? 4 : 0);
if (this.isBoosting) {
this.engineLight.color.setHex(0xff6b35);
this.engineGlow.material.color.setHex(0xff6b35);
} else {
this.engineLight.color.setHex(0x00e5ff);
this.engineGlow.material.color.setHex(0x00e5ff);
}
// Speed
let effectiveMaxSpeed = this.maxSpeed;
if (this.isBoosting) {
effectiveMaxSpeed = this.maxSpeed * BOOST_SPEED_MULT;
this.boostTimer -= dt;
if (this.boostTimer <= 0) this.endBoost();
}
// Gradual speed increase with distance
const speedProgress = this.distance / 5000;
const baseSpeed = PLAYER_SPEED_BASE + speedProgress * 60;
this.speed += (baseSpeed - this.speed) * 2 * dt;
if (this.maxSpeedReached < this.speed * 3.6) this.maxSpeedReached = this.speed * 3.6;
// Energy
this.energy -= ENERGY_DECAY_RATE * dt;
if (this.isBoosting) this.energy -= BOOST_CONSUME_RATE * dt;
// Boost charge (slow passive + near-miss bonuses)
if (!this.isBoosting) {
this.boost += (100 - this.boost) * 1.5 * dt;
}
this.distance += this.speed * dt;
return this.speed * dt;
}
startBoost() {
if (this.boost >= BOOST_MAX && !this.isBoosting && this.energy > 20) {
this.isBoosting = true;
this.boostTimer = BOOST_DURATION;
this.boost = 0;
audio.playBoost();
return true;
}
return false;
}
endBoost() {
this.isBoosting = false;
this.boostTimer = 0;
}
addEnergy(amount) {
this.energy = Math.min(100, this.energy + amount);
}
addBoost(amount) {
this.boost = Math.min(100, this.boost + amount);
}
isDead() {
return this.energy <= 0;
}
}
// ============================================================
// CHUNK SYSTEM
// ============================================================
class Chunk {
constructor(startZ, difficulty = 0) {
this.startZ = startZ;
this.meshes = [];
this.obstacles = [];
this.orbs = [];
this.debrisEmitters = [];
this.generate(difficulty);
}
generate(difficulty) {
// Canyon width decreases with difficulty
const widthShrink = difficulty * 8;
const halfWidth = CANON_HALF_WIDTH_BASE - Math.max(0, widthShrink);
const halfHeight = CANON_HEIGHT_BASE - Math.max(0, difficulty * 3);
// Generate canyon walls (procedural segments)
const segments = 8;
const segLength = CHUNK_LENGTH / segments;
let prevLeftX = -halfWidth;
let prevRightX = halfWidth;
let prevTopY = halfHeight;
let prevBottomY = -halfHeight + 2;
for (let i = 0; i < segments; i++) {
const z = this.startZ + i * segLength;
const nextLeftX = -halfWidth + (Math.random() - 0.5) * 10 * (1 - difficulty * 0.3);
const nextRightX = halfWidth + (Math.random() - 0.5) * 10 * (1 - difficulty * 0.3);
const nextTopY = halfHeight + (Math.random() - 0.5) * 8;
const nextBottomY = -halfHeight + 2 + (Math.random() - 0.5) * 5;
// Left wall
this.addWallSegment(z, prevLeftX, nextLeftX, prevBottomY, prevTopY, nextBottomY, nextTopY);
// Right wall
this.addWallSegment(z, prevRightX, nextRightX, prevBottomY, prevTopY, nextBottomY, nextTopY, true);
// Floor
this.addFloorSegment(z, prevLeftX, prevRightX, nextLeftX, nextRightX, prevBottomY, nextBottomY);
prevLeftX = nextLeftX;
prevRightX = nextRightX;
prevTopY = nextTopY;
prevBottomY = nextBottomY;
}
// Add obstacles based on difficulty
if (difficulty > 0.1) this.addFloatingMonoliths(halfWidth, halfHeight, difficulty);
if (difficulty > 0.3) this.addTightSqueezes(halfWidth, halfHeight, difficulty);
// Add orbs (energy pickups)
this.addOrbs(halfWidth, halfHeight, difficulty);
// Add debris emitters for visual effect
this.addDebrisEmitters(halfWidth, halfHeight);
}
addWallSegment(z, x1, x2, yBottom1, yTop1, yBottom2, yTop2, isRight = false) {
const segLength = CHUNK_LENGTH / 8;
const depth = 2 + Math.random() * 4;
const geo = new THREE.BufferGeometry();
const verts = new Float32Array([
x1, yTop1, z, x2, yTop2, z + segLength, x2, yBottom2, z + segLength,
x1, yTop1, z, x2, yBottom2, z + segLength, x1, yBottom1, z,
x1, yTop1, z + depth, x1, yBottom1, z + depth, x2, yBottom2, z + segLength + depth,
x1, yTop1, z + depth, x2, yBottom2, z + segLength + depth, x2, yTop2, z + segLength + depth,
]);
geo.setAttribute('position', new THREE.BufferAttribute(verts, 3));
geo.computeVertexNormals();
const mat = Math.random() > 0.5 ? matCanyonRock : matCanyonRockWarm;
const mesh = new THREE.Mesh(geo, mat);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
this.meshes.push(mesh);
// Add collision box
const minX = Math.min(x1, x2) - 0.5;
const maxX = Math.max(x1, x2) + 0.5;
const minY = Math.min(yBottom1, yBottom2);
const maxY = Math.max(yTop1, yTop2);
this.obstacles.push({ minX, maxX, minY, maxY, zStart: z, zEnd: z + segLength });
}
addFloorSegment(z, leftX1, rightX1, leftX2, rightX2, y1, y2) {
const segLength = CHUNK_LENGTH / 8;
const geo = new THREE.BufferGeometry();
const verts = new Float32Array([
leftX1, y1 - 0.5, z, rightX1, y1 - 0.5, z, rightX2, y2 - 0.5, z + segLength,
leftX1, y1 - 0.5, z, rightX2, y2 - 0.5, z + segLength, leftX2, y2 - 0.5, z + segLength
]);
geo.setAttribute('position', new THREE.BufferAttribute(verts, 3));
geo.computeVertexNormals();
const mesh = new THREE.Mesh(geo, matGround);
mesh.receiveShadow = true;
scene.add(mesh);
this.meshes.push(mesh);
// Floor collision
const minX = Math.min(leftX1, leftX2, rightX1, rightX2) - 0.5;
const maxX = Math.max(leftX1, leftX2, rightX1, rightX2) + 0.5;
this.obstacles.push({ minX, maxX, minY: -100, maxY: y1 - 0.8, zStart: z, zEnd: z + segLength });
}
addFloatingMonoliths(halfWidth, halfHeight, difficulty) {
const count = Math.floor(2 + difficulty * 4);
for (let i = 0; i < count; i++) {
const z = this.startZ + 10 + Math.random() * (CHUNK_LENGTH - 20);
const x = (Math.random() - 0.5) * (halfWidth * 1.4);
const y = -halfHeight + 5 + Math.random() * (halfHeight * 2);
const height = 6 + Math.random() * 8;
const geo = new THREE.CylinderGeometry(
0.8 + Math.random() * 1.2,
0.8 + Math.random() * 1.2,
height,
5 + Math.floor(Math.random() * 3)
);
const mesh = new THREE.Mesh(geo, matMonolith);
mesh.position.set(x, y, z);
mesh.rotation.y = Math.random() * Math.PI;
mesh.rotation.z = (Math.random() - 0.5) * 0.2;
mesh.castShadow = true;
scene.add(mesh);
this.meshes.push(mesh);
this.obstacles.push({
minX: x - 1.5, maxX: x + 1.5,
minY: y - height / 2, maxY: y + height / 2,
zStart: z - 1, zEnd: z + 1
});
}
}
addTightSqueezes(halfWidth, halfHeight, difficulty) {
const count = Math.floor(1 + difficulty * 2);
for (let i = 0; i < count; i++) {
const z = this.startZ + 15 + Math.random() * (CHUNK_LENGTH - 30);
// Create two pillars with a narrow gap
const gapX = (Math.random() - 0.5) * halfWidth * 0.6;
const gapWidth = 5 + (1 - difficulty) * 5;
const gapY = -halfHeight + 3 + Math.random() * halfHeight;
for (let side = -1; side <= 1; side += 2) {
const x = gapX + side * gapWidth;
const height = 8 + Math.random() * 6;
const mesh = new THREE.Mesh(geoMonolith.clone(), matMonolith);
mesh.position.set(x, gapY, z);
mesh.castShadow = true;
scene.add(mesh);
this.meshes.push(mesh);
this.obstacles.push({
minX: x - 1.5, maxX: x + 1.5,
minY: gapY - height / 2, maxY: gapY + height / 2,
zStart: z - 1, zEnd: z + 1
});
}
}
}
addOrbs(halfWidth, halfHeight, difficulty) {
const count = 4 + Math.floor(Math.random() * 4);
for (let i = 0; i < count; i++) {
const z = this.startZ + 5 + Math.random() * (CHUNK_LENGTH - 10);
// Place orbs in interesting spots - sometimes in narrow paths
const x = (Math.random() - 0.5) * halfWidth * 1.6;
const y = -halfHeight + 4 + Math.random() * (halfHeight * 1.6);
const mesh = new THREE.Mesh(geoOrb, matOrb);
mesh.position.set(x, y, z);
scene.add(mesh);
this.meshes.push(mesh);
this.orbs.push({ mesh, collected: false });
}
}
addDebrisEmitters(halfWidth, halfHeight) {
// Points along walls where debris particles spawn
const count = 3 + Math.floor(Math.random() * 4);
for (let i = 0; i < count; i++) {
this.debrisEmitters.push({
x: (Math.random() > 0.5 ? halfWidth : -halfWidth) + (Math.random() - 0.5) * 5,
y: (Math.random() - 0.5) * halfHeight * 1.5,
z: this.startZ + Math.random() * CHUNK_LENGTH,
timer: Math.random() * 2
});
}
}
dispose() {
for (const mesh of this.meshes) {
scene.remove(mesh);
if (mesh.geometry) mesh.geometry.dispose();
}
this.meshes = [];
this.obstacles = [];
this.orbs = [];
this.debrisEmitters = [];
}
}
// ============================================================
// PARTICLE SYSTEM
// ============================================================
class ParticleSystem {
constructor() {
// Engine trail
this.trailPool = [];
const trailGeo = new THREE.SphereGeometry(0.12, 3, 3);
for (let i = 0; i < 80; i++) {
const mesh = new THREE.Mesh(trailGeo, new THREE.MeshBasicMaterial({
color: 0x00e5ff, transparent: true, opacity: 0.7
}));
mesh.visible = false;
scene.add(mesh);
this.trailPool.push({ mesh, life: 0, maxLife: 0.6 + Math.random() * 0.4 });
}
// Debris particles
this.debrisPool = [];
for (let i = 0; i < 100; i++) {
const mesh = new THREE.Mesh(geoDebris, matDebris.clone());
mesh.visible = false;
scene.add(mesh);
this.debrisPool.push({ mesh, life: 0, vx: 0, vy: 0, vz: 0 });
}
this.trailTimer = 0;
}
emitTrail(pos, isBoosting) {
this.trailTimer += 0.016;
const rate = isBoosting ? 0.02 : 0.04;
if (this.trailTimer < rate) return;
this.trailTimer = 0;
const p = this.trailPool.find(p => p.life <= 0);
if (!p) return;
p.mesh.position.copy(pos);
p.mesh.position.z += 2.5 + Math.random() * 1;
p.mesh.position.x += (Math.random() - 0.5) * 0.3;
p.mesh.position.y += (Math.random() - 0.5) * 0.3;
p.life = p.maxLife;
p.mesh.visible = true;
p.mesh.material.color.setHex(isBoosting ? 0xff6b35 : 0x00e5ff);
p.mesh.material.opacity = 0.8;
p.mesh.scale.setScalar(isBoosting ? 1.8 : 1);
}
emitDebris(pos, speed) {
const p = this.debrisPool.find(p => p.life <= 0);
if (!p) return;
p.mesh.position.copy(pos);
p.mesh.position.x += (Math.random() - 0.5) * 8;
p.mesh.position.y += (Math.random() - 0.5) * 8;
p.vx = (Math.random() - 0.5) * speed * 0.3;
p.vy = (Math.random() - 0.5) * speed * 0.3;
p.vz = -speed * 0.5 - Math.random() * speed * 0.5;
p.life = 1.0;
p.mesh.visible = true;
p.mesh.material.opacity = 0.6;
}
update(dt, playerSpeed, playerPos) {
// Trail
for (const p of this.trailPool) {
if (p.life <= 0) continue;
p.life -= dt;
if (p.life <= 0) {
p.mesh.visible = false;
continue;
}
// Move trail back relative to player speed
p.mesh.position.z += playerSpeed * dt * 0.5;
p.mesh.material.opacity = (p.life / p.maxLife) * 0.7;
p.mesh.scale.multiplyScalar(1 - dt * 0.5);
}
// Debris
for (const p of this.debrisPool) {
if (p.life <= 0) continue;
p.life -= dt;
if (p.life <= 0) {
p.mesh.visible = false;
continue;
}
p.mesh.position.x += p.vx * dt;
p.mesh.position.y += p.vy * dt;
p.mesh.position.z += p.vz * dt;
p.mesh.rotation.x += dt * 8;
p.mesh.rotation.z += dt * 6;
p.mesh.material.opacity = p.life * 0.6;
}
}
dispose() {
for (const p of this.trailPool) {
scene.remove(p.mesh);
p.mesh.geometry.dispose();
p.mesh.material.dispose();
}
for (const p of this.debrisPool) {
scene.remove(p.mesh);
p.mesh.material.dispose();
}
}
}
// ============================================================
// CAMERA CONTROLLER
// ============================================================
class CameraController {
constructor() {
this.shakeAmount = 0;
this.shakeDecay = 8;
this.targetFOV = 70;
this.currentFOV = 70;
}
shake(intensity) {
this.shakeAmount = Math.min(this.shakeAmount + intensity, 3);
}
update(dt, playerPos, isBoosting, playerSpeed) {
// Follow player
const targetX = playerPos.x * 0.3;
const targetY = playerPos.y * 0.4 + 8;
camera.position.x += (targetX - camera.position.x) * 6 * dt;
camera.position.y += (targetY - camera.position.y) * 5 * dt;
camera.position.z = playerPos.z - 22 - (isBoosting ? 4 : 0);
// Look ahead
const lookTarget = new THREE.Vector3(
playerPos.x * 0.2,
playerPos.y * 0.3 + 2,
playerPos.z + 80
);
camera.lookAt(lookTarget);
// Shake
if (this.shakeAmount > 0) {
camera.position.x += (Math.random() - 0.5) * this.shakeAmount;
camera.position.y += (Math.random() - 0.5) * this.shakeAmount * 0.6;
this.shakeAmount *= Math.exp(-this.shakeDecay * dt);
if (this.shakeAmount < 0.02) this.shakeAmount = 0;
}
// FOV for speed/boost effect
const speedRatio = playerSpeed / PLAYER_SPEED_MAX;
this.targetFOV = 70 + speedRatio * 10 + (isBoosting ? 15 : 0);
this.currentFOV += (this.targetFOV - this.currentFOV) * 8 * dt;
camera.fov = this.currentFOV;
camera.updateProjectionMatrix();
}
}
// ============================================================
// COLLISION SYSTEM
// ============================================================
function checkCollisions(player, chunks, particles) {
const px = player.mesh.position.x;
const py = player.mesh.position.y;
const pz = player.mesh.position.z - 1.5;
const playerRadius = 1.2;
let nearestDistance = Infinity;
let hadNearMiss = false;
for (const chunk of chunks) {
// Wall collisions
for (const obs of chunk.obstacles) {
if (pz < obs.zStart - 1 || pz > obs.zEnd + 1) continue;
// Check X bounds
if (px + playerRadius > obs.minX && px - playerRadius < obs.maxX) {
// Check Y bounds
if (py + playerRadius > obs.minY && py - playerRadius < obs.maxY) {
return { dead: true, reason: 'collision' };
}
// Near miss check
const edgeDist = Math.min(
Math.abs(px - obs.minX) - playerRadius,
Math.abs(px - obs.maxX) - playerRadius,
Math.abs(py - obs.minY) - playerRadius,
Math.abs(py - obs.maxY) - playerRadius
);
if (edgeDist >= 0 && edgeDist < NEAR_MISS_THRESHOLD) {
nearestDistance = Math.min(nearestDistance, edgeDist);
}
}
}
// Orb collection
for (const orb of chunk.orbs) {
if (orb.collected) continue;
const dx = px - orb.mesh.position.x;
const dy = py - orb.mesh.position.y;
const dz = pz - orb.mesh.position.z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
if (dist < 2.5) {
orb.collected = true;
orb.mesh.visible = false;
player.addEnergy(ORB_CHARGE);
player.addBoost(10);
score.addEnergyCollect();
audio.playCollect();
// Spawn collect particles
for (let i = 0; i < 8; i++) {
particles.emitDebris(orb.mesh.position, player.speed);
}
}
}
}
// Near miss scoring
if (nearestDistance < NEAR_MISS_THRESHOLD && nearestDistance >= 0) {
hadNearMiss = true;
score.addNearMiss(nearestDistance);
cam.shake(0.5 + (NEAR_MISS_THRESHOLD - nearestDistance) * 0.3);
if (nearestDistance < NEAR_MISS_BONUS_THRESHOLD) {
audio.playNearMiss();
document.getElementById('nearMissFlash').style.opacity = '0.4';
setTimeout(() => {
document.getElementById('nearMissFlash').style.opacity = '0';
}, 150);
}
}
return { dead: false, hadNearMiss };
}
// ============================================================
// SCORE SYSTEM
// ============================================================
const score = {
value: 0,
multiplier: 1.0,
nearMissCounter: 0,
energyCollected: 0,
maxMultiplier: 1.0,
update(dt, playerSpeed) {
// Base score from distance
this.value += (playerSpeed * dt) * this.multiplier * 0.5;
// Multiplier decay
if (this.multiplier > 1) {
this.multiplier -= dt * 0.15;
if (this.multiplier < 1) this.multiplier = 1;
}
},
addNearMiss(distance) {
this.nearMissCounter++;
const bonus = 1 + (NEAR_MISS_THRESHOLD - distance) * 0.5;
this.multiplier = Math.min(this.multiplier + bonus * 0.3, 10);
if (this.multiplier > this.maxMultiplier) this.maxMultiplier = this.multiplier;
},
addEnergyCollect() {
this.energyCollected++;
},
reset() {
this.value = 0;
this.multiplier = 1.0;
this.nearMissCounter = 0;
this.energyCollected = 0;
this.maxMultiplier = 1.0;
}
};
// ============================================================
// GAME VARIABLES
// ============================================================
let gameState = STATE.STARTED;
let chunks = [];
let player;
let particles;
let cam = new CameraController();
let audio = new AudioSys();
let lastTime = 0;
let difficulty = 0;
// Mouse state
let mouseX = 0;
let mouseY = 0;
let mouseNormX = 0;
let mouseNormY = 0;
let mouseDown = false;
let keysPressed = {};
// ============================================================
// INIT
// ============================================================
function init() {
player = new Player();
particles = new ParticleSystem();
// Generate initial chunks
for (let i = 0; i < CHUNK_COUNT; i++) {
const diff = Math.min(1, i / CHUNK_COUNT);
chunks.push(new Chunk(i * CHUNK_LENGTH, diff));
}
// Set player starting Z
player.mesh.position.z = 20;
}
// ============================================================
// CHUNK MANAGEMENT
// ============================================================
function updateChunks(dt, distanceTraveled) {
// Move chunks backward as player moves forward
const playerZ = player.mesh.position.z;
// Remove chunks behind player
while (chunks.length > 0 && chunks[0].startZ + CHUNK_LENGTH < playerZ - 50) {
chunks.shift().dispose();
}
// Add chunks ahead
const furthestZ = chunks[chunks.length - 1].startZ;
while (furthestZ < playerZ + CHUNK_COUNT * CHUNK_LENGTH) {
const diff = Math.min(1, (furthestZ - 20) / 3000);
chunks.push(new Chunk(furthestZ, diff));
difficulty = diff;
}
}
// ============================================================
// HUD UPDATE
// ============================================================
function updateHUD() {
const speedKmh = Math.round(player.speed * 3.6);
const speedPercent = (player.speed / (player.maxSpeed * BOOST_SPEED_MULT)) * 100;
document.getElementById('scoreText').textContent = Math.round(score.value).toLocaleString();
document.getElementById('speedText').textContent = speedKmh + ' km/h';
document.getElementById('speedBar').style.height = speedPercent + '%';
// Multiplier display
const multEl = document.getElementById('multiplierText');
multEl.textContent = 'x' + score.multiplier.toFixed(1);
if (score.multiplier > 1.5) {
const pulseScale = 1 + (score.multiplier - 1.5) * 0.15;
multEl.style.transform = `scale(${pulseScale})`;
multEl.style.color = score.multiplier > 5 ? '#ff1744' : '#ff6b35';
} else {
multEl.style.transform = 'scale(1)';
multEl.style.color = '#ff6b35';
}
// Energy bar
const energyEl = document.getElementById('energyBar');
energyEl.style.width = Math.max(0, player.energy) + '%';
energyEl.classList.toggle('critical', player.energy < 25);
// Boost bar
const boostEl = document.getElementById('boostBar');
boostEl.style.height = player.boost + '%';
document.getElementById('boostReadyText').style.opacity = player.boost >= BOOST_MAX ? '1' : '0';
}
// ============================================================
// GAME OVER
// ============================================================
function showGameOver() {
gameState = STATE.GAMEOVER;
audio.playCrash();
document.getElementById('hud').style.display = 'none';
document.getElementById('gameOver').style.display = 'flex';
document.getElementById('finalScore').textContent = Math.round(player.distance).toLocaleString();
document.getElementById('finalSpeed').textContent = Math.round(player.maxSpeedReached).toLocaleString();
document.getElementById('finalMult').textContent = 'x' + score.maxMultiplier.toFixed(1);
document.getElementById('finalNearMisses').textContent = score.nearMissCounter;
}
function resetGame() {
// Clean up
if (player) player.mesh.parent?.remove(player.mesh);
if (particles) particles.dispose();
for (const chunk of chunks) chunk.dispose();
chunks = [];
score.reset();
difficulty = 0;
// Re-init
init();
camera.position.set(0, 8, 0);
camera.fov = 70;
camera.updateProjectionMatrix();
document.getElementById('hud').style.display = 'block';
document.getElementById('gameOver').style.display = 'none';
gameState = STATE.PLAYING;
}
// ============================================================
// INPUT HANDLING
// ============================================================
window.addEventListener('mousemove', (e) => {
if (gameState !== STATE.PLAYING) return;
// Map mouse position to normalized -1..1 range, then scale to gameplay area
mouseNormX = (e.clientX / window.innerWidth) * 2 - 1;
mouseNormY = (e.clientY / window.innerHeight) * 2 - 1;
// Scale based on canyon width (decreases with difficulty)
const scale = CANON_HALF_WIDTH_BASE - difficulty * 5;
mouseX = mouseNormX * scale;
mouseY = mouseNormY * scale;
});
window.addEventListener('mousedown', () => {
mouseDown = true;
if (gameState === STATE.PLAYING) player.startBoost();
});
window.addEventListener('mouseup', () => {
mouseDown = false;
if (gameState === STATE.PLAYING) player.endBoost();
});
window.addEventListener('keydown', (e) => {
keysPressed[e.key.toLowerCase()] = true;
if (e.key === 'f' && gameState === STATE.PLAYING) player.startBoost();
});
window.addEventListener('keyup', (e) => {
keysPressed[e.key.toLowerCase()] = false;
});
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
// Start button
document.getElementById('startBtn').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
audio.init();
init();
gameState = STATE.PLAYING;
lastTime = performance.now();
});
// Restart button
document.getElementById('restartBtn').addEventListener('click', () => {
resetGame();
lastTime = performance.now();
});
// ============================================================
// RENDER LOOP
// ============================================================
function render(timestamp) {
requestAnimationFrame(render);
if (gameState !== STATE.PLAYING) {
// Still render the scene in background for start screen
if (gameState === STATE.STARTED && chunks.length === 0) {
init();
player.mesh.position.z = 20;
}
composer.render();
return;
}
const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
lastTime = timestamp;
// Move player forward
const distanceTraveled = player.update(dt, mouseX, mouseY);
player.mesh.position.z += distanceTraveled;
// Update chunks
updateChunks(dt, distanceTraveled);
// Update difficulty based on distance
difficulty = Math.min(1, player.distance / 3000);
// Check collisions
const collisionResult = checkCollisions(player, chunks, particles);
if (collisionResult.dead || player.isDead()) {
showGameOver();
composer.render();
return;
}
// Update score
score.update(dt, player.speed);
// Animate orbs
for (const chunk of chunks) {
for (const orb of chunk.orbs) {
if (orb.collected) continue;
orb.mesh.rotation.y += dt * 3;
orb.mesh.position.y += Math.sin(timestamp * 0.003 + orb.mesh.position.x) * 0.01;
}
}
// Particles
particles.emitTrail(player.mesh.position, player.isBoosting);
for (const chunk of chunks) {
for (const emitter of chunk.debrisEmitters) {
emitter.timer -= dt * 5;
if (emitter.timer <= 0 && Math.abs(emitter.z - player.mesh.position.z) < 60) {
particles.emitDebris(
new THREE.Vector3(emitter.x, emitter.y, emitter.z),
player.speed
);
emitter.timer = 0.3 + Math.random() * 0.5;
}
}
}
particles.update(dt, player.speed, player.mesh.position);
// Camera
cam.update(dt, player.mesh.position, player.isBoosting, player.speed);
// Audio update
const speedRatio = player.speed / player.maxSpeed;
audio.updateEngine(speedRatio);
audio.updateWind(speedRatio);
// HUD
updateHUD();
// Fog adjustment
const fogDensity = 0.006 + (player.isBoosting ? 0.004 : 0) - difficulty * 0.001;
scene.fog.density = fogDensity;
// Render
composer.render();
}
requestAnimationFrame(render);
</script>
</body>
</html>
<!-- agent-meta {"model":"Qwen3.6-27B-Fable-Fusion-711-Uncensored-Heretic-NM-DAU-NEO-MAX-MTP-GGUF","provider":"brigid","persona":"full","sessionId":"934732f0-437c-4ae6-bb8d-bd39f1fbb19c","tokensIn":615229,"tokensOut":19946,"tokensTotal":635175,"cost":0,"turns":25,"toolCalls":24,"failedToolCalls":0,"timestamp":"2026-08-06T02:53:11.035Z"} -->