← Canyon Run results

Canyon Run

Qwen 3.6 27b MTP · full

Initial view of Canyon Run

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 33,885 bytes · SHA-256 7fa750fc6d64
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CANYON RACER</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{overflow:hidden;background:#0a0015;font-family:'Courier New',monospace;user-select:none}
canvas{display:block}

#hud{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10}

.bar-wrap{position:absolute;height:4px;background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden}
.bar-fill{height:100%;border-radius:2px;transition:width .1s}
.bar-label{color:rgba(255,255,255,.35);font-size:9px;letter-spacing:4px;text-transform:uppercase;margin-bottom:6px}

#speed-wrap{bottom:30px;right:40px;width:180px}
#speed-fill{background:linear-gradient(90deg,#ff6a00,#ee0979)}

#energy-wrap{bottom:30px;left:40px;width:180px}
#energy-fill{background:linear-gradient(90deg,#00d4ff,#7b2ff7)}

#turbo-wrap{bottom:65px;left:40px;right:40px;width:auto}
#turbo-fill{background:linear-gradient(90deg,#ff6a00,#ff0044);width:0%}

#score-display{position:absolute;top:24px;left:50%;transform:translateX(-50%);color:rgba(255,255,255,.7);font-size:16px;letter-spacing:5px;text-shadow:0 0 20px rgba(255,140,0,.3)}

#multiplier{position:absolute;top:38%;left:50%;transform:translate(-50%,-50%);font-size:72px;color:transparent;font-weight:bold;letter-spacing:-2px;transition:all .15s ease-out}
#multiplier.active{color:rgba(255,160,50,.9);text-shadow:0 0 80px rgba(255,100,0,.7)}

#start-screen,#game-over{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:100;pointer-events:all}
#start-screen{background:radial-gradient(ellipse at 50% 60%,#1a0533 0%,#0a0015 70%)}
#game-over{display:none;background:rgba(5,0,15,.92)}

.title{font-size:58px;color:#ff8c00;letter-spacing:10px;text-shadow:0 0 60px rgba(255,140,0,.5);margin-bottom:8px}
.subtitle{font-size:12px;color:rgba(255,255,255,.35);letter-spacing:7px;margin-bottom:50px}

.btn{padding:14px 48px;background:transparent;border:1px solid rgba(255,140,0,.5);color:#ff8c00;font-size:14px;letter-spacing:5px;cursor:pointer;font-family:'Courier New',monospace;transition:all .3s}
.btn:hover{background:rgba(255,140,0,.12);border-color:#ff8c00}

.controls-info{margin-top:36px;color:rgba(255,255,255,.25);font-size:11px;letter-spacing:2px;text-align:center;line-height:2.2}
.go-title{font-size:44px;color:#ff3366;letter-spacing:8px;margin-bottom:36px}
.stat-line{color:rgba(255,255,255,.5);font-size:14px;letter-spacing:3px;margin:6px 0}
.stat-val{color:#ff8c00}

#near-miss-flash,#boost-flash{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:5;opacity:0;transition:opacity .1s}
#near-miss-flash{background:radial-gradient(ellipse at center,rgba(255,140,0,.3),transparent 70%)}
#boost-flash{background:radial-gradient(ellipse at center,rgba(255,50,0,.12),transparent 60%);transition:opacity .2s}

#vignette{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:4;
  background:radial-gradient(ellipse at center,transparent 50%,rgba(0,0,0,.6) 100%)}

#speed-lines{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:3;opacity:0}
</style>
</head>
<body>

<div id="vignette"></div>

<div id="start-screen">
  <div class="title">CANYON RACER</div>
  <div class="subtitle">PROCEDURAL HIGH-SPEED FLIGHT</div>
  <button class="btn" id="start-btn">LAUNCH</button>
  <div class="controls-info">WASD / ARROWS — STEER &nbsp;&nbsp; SHIFT — BOOST &nbsp;&nbsp; SPACE — BRAKE</div>
</div>

<div id="game-over">
  <div class="go-title">SIGNAL LOST</div>
  <div class="stat-line">SCORE: <span class="stat-val" id="final-score">0</span></div>
  <div class="stat-line">MAX SPEED: <span class="stat-val" id="max-speed">0</span></div>
  <div class="stat-line">DISTANCE: <span class="stat-val" id="final-distance">0</span></div>
  <button class="btn" id="restart-btn" style="margin-top:36px">RELAUNCH</button>
</div>

<div id="hud">
  <div id="score-display">SCORE: 0</div>
  <div id="multiplier"></div>

  <div id="speed-wrap">
    <div class="bar-label">VELOCITY</div>
    <div class="bar-wrap"><div class="bar-fill" id="speed-fill"></div></div>
  </div>
  <div id="energy-wrap">
    <div class="bar-label">ENERGY</div>
    <div class="bar-wrap"><div class="bar-fill" id="energy-fill" style="width:100%"></div></div>
  </div>
  <div id="turbo-wrap">
    <div class="bar-wrap"><div class="bar-fill" id="turbo-fill"></div></div>
  </div>
</div>

<div id="near-miss-flash"></div>
<div id="boost-flash"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function(){'use strict';

// ════════════════════════════════════════
// CONSTANTS
// ════════════════════════════════════════
const CHUNK_LEN = 200;
const NUM_CHUNKS = 14;
const CANYON_W = 70;
const CANYON_H = 55;
const BASE_SPEED = 1.8;
const TOP_SPEED = 3.5;
const BOOST_MULT = 2.0;
const ENERGY_DECAY = 2.5;
const NEAR_MISS_DIST = 9;
const COLLIDE_DIST = 3.0;

// ════════════════════════════════════════
// GAME STATE
// ════════════════════════════════════════
let running = false, score=0, mult=1, energy=100, turbo=0, spd=BASE_SPEED;
let maxSpd=0, distTraveled=0, difficulty=1;

// ════════════════════════════════════════
// THREE.JS CORE
// ════════════════════════════════════════
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x1a0533, 0.003);

const camera = new THREE.PerspectiveCamera(78, innerWidth/innerHeight, 0.1, 900);
camera.position.set(0,6,-14);

const renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.0;
document.body.appendChild(renderer.domElement);

// ════════════════════════════════════════
// LIGHTING — Golden Hour / Cyber Sunset
// ════════════════════════════════════════
scene.add(new THREE.AmbientLight(0x2a1050, 0.6));

const sun = new THREE.DirectionalLight(0xff8c42, 1.8);
sun.position.set(30, 70, -80);
sun.castShadow = true;
sun.shadow.mapSize.set(2048,2048);
Object.assign(sun.shadow.camera,{near:1,far:350,left:-80,right:80,top:80,bottom:-80});
scene.add(sun);

const rim = new THREE.DirectionalLight(0x7b2ff7, 0.4);
rim.position.set(-40, 15, 60);
scene.add(rim);

// ════════════════════════════════════════
// SKY DOME (gradient shader)
// ════════════════════════════════════════
const skyGeo = new THREE.SphereGeometry(450,16,8);
const skyMat = new THREE.ShaderMaterial({
  side:THREE.BackSide, depthWrite:false,
  vertexShader:`varying vec3 vWP;void main(){vWP=position;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.);}`,
  fragmentShader:`
    varying vec3 vWP;
    void main(){
      float h=normalize(vWP).y;
      vec3 c=mix(vec3(.6,.25,.08),vec3(.35,.08,.28),smoothstep(-.1,.3,h));
      c=mix(c,vec3(.04,0.,.12),smoothstep(.2,.7,h));
      // sun glow
      vec3 sd=normalize(vec3(30.,70.,-80.));
      float sun=pow(max(dot(normalize(vWP),sd),0.),64.);
      c+=vec3(1.,.5,.2)*sun*2.;
      gl_FragColor=vec4(c,1.);
    }`
});
const sky = new THREE.Mesh(skyGeo,skyMat);
scene.add(sky);

// ════════════════════════════════════════
// PLAYER CRAFT — Sleek low-poly fighter
// ════════════════════════════════════════
let playerGrp;
function buildPlayer(){
  const g = new THREE.Group();

  // Fuselage
  const bodyG = new THREE.ConeGeometry(.5,3.2,6);
  bodyG.rotateX(Math.PI/2);
  const bodyM = new THREE.MeshPhongMaterial({color:0xbbbbcc,flatShading:true,shininess:120,specular:0xff8c00});
  g.add(new THREE.Mesh(bodyG,bodyM));

  // Nose tip (bright)
  const noseG = new THREE.ConeGeometry(.15,.6,4);
  noseG.rotateX(Math.PI/2);
  const noseM = new THREE.MeshPhongMaterial({color:0xff8c00,emissive:0xff4400,emissiveIntensity:.3});
  const nose = new THREE.Mesh(noseG,noseM);
  nose.position.z=1.9;
  g.add(nose);

  // Wings — swept back
  const wingShape=new THREE.Shape();
  wingShape.moveTo(0,.15);wingShape.lineTo(-3.2,-1.8);wingShape.lineTo(-2.8,-.6);wingShape.lineTo(0,.4);
  const wGeo=new THREE.ExtrudeGeometry(wingShape,{depth:.06,bevelEnabled:false});
  const wMat=new THREE.MeshPhongMaterial({color:0x3a3a5c,flatShading:true});

  const lw=new THREE.Mesh(wGeo,wMat);lw.position.set(.2,-.1,-.7);g.add(lw);
  const rw=lw.clone();rw.scale.x=-1;rw.position.x=-.2;g.add(rw);

  // Tail fins
  const tailShape=new THREE.Shape();
  tailShape.moveTo(0,0);tailShape.lineTo(-1.2,-1.5);tailShape.lineTo(-1.0,-.4);tailShape.lineTo(0,.2);
  const tGeo=new THREE.ExtrudeGeometry(tailShape,{depth:.05,bevelEnabled:false});

  const tf=new THREE.Mesh(tGeo,wMat);tf.position.set(.15,.3,-1.6);g.add(tf);
  const tr=tf.clone();tr.scale.x=-1;tr.position.x=-.15;g.add(tr);

  // Engine pods + glow
  const podG=new THREE.CylinderGeometry(.2,.3,1.2,6);
  podG.rotateX(Math.PI/2);
  const podM=new THREE.MeshPhongMaterial({color:0x444455,flatShading:true});

  [-.35,.35].forEach(x=>{
    const p=new THREE.Mesh(podG,podM);p.position.set(x,-.15,-1.2);g.add(p);
    // glow sphere
    const gl=new THREE.Mesh(new THREE.SphereGeometry(.2,6,4),new THREE.MeshBasicMaterial({color:0xff6a00}));
    gl.position.set(x,-.15,-1.85);g.add(gl);
  });

  // Engine point light
  const eL=new THREE.PointLight(0xff6a00,2,18);eL.position.z=-2;g.add(eL);

  scene.add(g);
  return g;
}

// ════════════════════════════════════════
// PHYSICS STATE
// ════════════════════════════════════════
const phys={
  pos:new THREE.Vector3(0,5,0),
  vel:new THREE.Vector3(),
  roll:0,pitch:0,targetRoll:0,targetPitch:0
};

// ════════════════════════════════════════
// INPUT
// ════════════════════════════════════════
const keys={};
addEventListener('keydown',e=>{keys[e.code]=true});
addEventListener('keyup',e=>{keys[e.code]=false});

// ════════════════════════════════════════
// PROCEDURAL CHUNK GENERATION
// ════════════════════════════════════════
let chunks=[], chunkIdx=0;

function createChunk(zPos){
  const grp=new THREE.Group();
  grp.position.z=zPos;

  const df=Math.min(difficulty,6);
  const w=CANYON_W-df*7;
  const h=CANYON_H-df*3;

  buildWalls(grp,w,h,zPos);

  // Monoliths
  if(Math.random()<.25+df*.08) addMonoliths(grp,w,h,zPos);
  // Tight squeezes
  if(df>1.5&&Math.random()<.18) addSqueeze(grp,w,h,zPos);
  // Energy orbs
  for(let i=0;i<1+~~(Math.random()*3);i++) addOrb(grp,w,h,zPos);

  scene.add(grp);
  return{grp,z:zPos,idx:chunkIdx++};
}

// Simple hash-based pseudo-random for consistent generation
function hash(n){let s=Math.sin(n)*43758.5453;return s-Math.floor(s)}

function buildWalls(grp,w,h,zBase){
  const segs=24, sl=CHUNK_LEN/segs;
  const ptsL=[],ptsR=[];

  for(let i=0;i<=segs;i++){
    const t=i/segs, z=-sl*i;
    // Use hash-based noise for consistent generation per chunk
    const seed=zBase*0.1+i;
    const nL=(hash(seed)-.5)*w*.45;
    const nR=(hash(seed+999)-.5)*w*.45;
    const hL=(hash(seed+500)-.5)*h*.35;
    const hR=(hash(seed+1500)-.5)*h*.35;

    ptsL.push(new THREE.Vector3(-w/2+nL, h/2+hL, z));
    ptsR.push(new THREE.Vector3( w/2+nR, h/2+hR, z));
  }

  // Wall materials — warm canyon colors
  const wm1=new THREE.MeshPhongMaterial({color:0x8b5e3c,flatShading:true});
  const wm2=new THREE.MeshPhongMaterial({color:0x7a4e2c,flatShading:true});
  const wm3=new THREE.MeshPhongMaterial({color:0x6d4020,flatShading:true});

  for(let i=0;i<segs;i++){
    const p1=ptsL[i],p2=ptsL[i+1];
    const b1=new THREE.Vector3(p1.x,-h*.5,p1.z);
    const b2=new THREE.Vector3(p2.x,-h*.5,p2.z);

    // Left wall face (top)
    addTri(grp,p1,p2,b1, i%3===0?wm1:wm2);
    addTri(grp,p2,new THREE.Vector3(p2.x,Math.max(0,h*.1),p2.z),b2, wm3);

    // Right wall
    const r1=ptsR[i],r2=ptsR[i+1];
    const rb1=new THREE.Vector3(r1.x,-h*.5,r1.z);
    const rb2=new THREE.Vector3(r2.x,-h*.5,r2.z);

    addTri(grp,r1,r2,rb1, i%3===0?wm2:wm1);
    addTri(grp,r2,new THREE.Vector3(r2.x,Math.max(0,h*.1),r2.z),rb2, wm3);
  }

  // Canyon floor — dark ground plane for this chunk
  const floorGeo=new THREE.PlaneGeometry(w*1.5,CHUNK_LEN+10);
  floorGeo.rotateX(-Math.PI/2);
  const floorMat=new THREE.MeshPhongMaterial({color:0x2a1810,flatShading:true});
  const floor=new THREE.Mesh(floorGeo,floorMat);
  floor.position.y=-h*.5;
  floor.receiveShadow=true;
  grp.add(floor);

  // Store collision data
  grp.userData.walls={ptsL,ptsR,h};
}

function addTri(grp,a,b,c,mat){
  const g=new THREE.BufferGeometry();
  g.setAttribute('position',new THREE.BufferAttribute(new Float32Array([
    a.x,a.y,a.z,b.x,b.y,b.z,c.x,c.y,c.z
  ]),3));
  g.computeVertexNormals();
  const m=new THREE.Mesh(g,mat);
  m.castShadow=true;m.receiveShadow=true;
  grp.add(m);
}

function addMonoliths(grp,w,h,zBase){
  const cnt=1+~~(Math.random()*2);
  if(!grp.userData.obs) grp.userData.obs=[];

  for(let i=0;i<cnt;i++){
    const x=(hash(zBase*7+i*3)-.5)*w*.6;
    const z=(hash(zBase*13+i*7)-.5)*CHUNK_LEN*.8;
    const mh=8+Math.random()*22;

    // Tall geometric pillar — octagonal
    const g=new THREE.CylinderGeometry(1.5,2,mh,6);
    const m=new THREE.MeshPhongMaterial({color:0x9b59b6,flatShading:true,emissive:0x2a1040,emissiveIntensity:.3});
    const mesh=new THREE.Mesh(g,m);
    mesh.position.set(x,-h*.5+mh/2,z);
    mesh.rotation.y=Math.random()*Math.PI;
    mesh.castShadow=true;
    grp.add(mesh);

    // Add glowing top cap
    const capG=new THREE.OctahedronGeometry(1.2,0);
    const capM=new THREE.MeshBasicMaterial({color:0xcc66ff});
    const cap=new THREE.Mesh(capG,capM);
    cap.position.y=mh/2;
    mesh.add(cap);

    grp.userData.obs.push({mesh,type:'monolith'});
  }
}

function addSqueeze(grp,w,h,zBase){
  if(!grp.userData.obs) grp.userData.obs=[];

  const sz=(hash(zBase*17)-.5)*CHUNK_LEN*.6;
  const gapW=Math.max(6,18-difficulty*2);
  const cx=(hash(zBase*23)-.5)*w*.25;

  // Two large barriers with a narrow gap
  const barrierG=new THREE.BoxGeometry(w*.4,h*1.1,gapW+4);
  const barrierM=new THREE.MeshPhongMaterial({color:0x6d4020,flatShading:true});

  [-1,1].forEach(side=>{
    const b=new THREE.Mesh(barrierG,barrierM);
    b.position.set(cx+side*(gapW/2+w*.2),0,sz);
    b.castShadow=true;b.receiveShadow=true;
    grp.add(b);
    grp.userData.obs.push({mesh:b,type:'squeeze'});
  });

  // Orb in the gap as incentive
  addOrb(grp,w,h,zBase,cx,-h*.5+10,sz,true);
}

function addOrb(grp,w,h,zBase,fx,fy,fz){
  const x=fx!==undefined?fx:(hash(zBase*31)-.5)*w*.6;
  const y=fy!==undefined?fy:2+(hash(zBase*37)-.5)*h*.4+5;
  const z=fz!==undefined?fz:(hash(zBase*41)-.5)*CHUNK_LEN*.8;

  // Octahedron orb
  const g=new THREE.OctahedronGeometry(.7,0);
  const m=new THREE.MeshBasicMaterial({color:0x00d4ff});
  const mesh=new THREE.Mesh(g,m);
  mesh.position.set(x,y,z);

  // Glow shell
  const glowG=new THREE.SphereGeometry(1.3,6,4);
  const glowM=new THREE.MeshBasicMaterial({color:0x00d4ff,transparent:true,opacity:.15});
  mesh.add(new THREE.Mesh(glowG,glowM));

  // Point light
  mesh.add(new THREE.PointLight(0x00d4ff,.8,12));

  grp.add(mesh);
  if(!grp.userData.orbs) grp.userData.orbs=[];
  grp.userData.orbs.push({mesh,collected:false});
}

// ════════════════════════════════════════
// PARTICLE SYSTEMS
// ════════════════════════════════════════
let trailPts,trailPosArr,trailAlphaArr,trailIdx=0;

function initTrail(){
  const N=300;
  const g=new THREE.BufferGeometry();
  const pos=new Float32Array(N*3);
  const sz=new Float32Array(N);
  const al=new Float32Array(N);
  for(let i=0;i<N;i++){pos[i*3+1]=-999;sz[i]=Math.random()*4+1;}

  g.setAttribute('position',new THREE.BufferAttribute(pos,3));
  g.setAttribute('size',new THREE.BufferAttribute(sz,1));
  g.setAttribute('alpha',new THREE.BufferAttribute(al,1));

  const mat=new THREE.ShaderMaterial({
    vertexShader:`attribute float size;attribute float alpha;varying float vA;void main(){vA=alpha;vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=size*(250./-mv.z);gl_Position=projectionMatrix*mv;}`,
    fragmentShader:`varying float vA;void main(){float d=length(gl_PointCoord-.5);if(d>.5)discard;float a=smoothstep(.5,.1,d)*vA;gl_FragColor=vec4(1.,.35,.08,a);}`,
    transparent:true,depthWrite:false,blending:THREE.AdditiveBlending
  });

  trailPts=new THREE.Points(g,mat);scene.add(trailPts);
  trailPosArr=pos;trailAlphaArr=al;
}

// Speed/debris particles
let spdPts,spdPosArr;
function initSpeedParticles(){
  const N=200;
  const g=new THREE.BufferGeometry();
  const pos=new Float32Array(N*3);
  const sz=new Float32Array(N);
  for(let i=0;i<N;i++){resetSpdP(pos,i);sz[i]=Math.random()*2+.5;}

  g.setAttribute('position',new THREE.BufferAttribute(pos,3));
  g.setAttribute('size',new THREE.BufferAttribute(sz,1));

  const mat=new THREE.ShaderMaterial({
    vertexShader:`attribute float size;void main(){vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=size*(180./-mv.z);gl_Position=projectionMatrix*mv;}`,
    fragmentShader:`void main(){float d=length(gl_PointCoord-.5);if(d>.5)discard;float a=smoothstep(.5,.1,d)*.25;gl_FragColor=vec4(.7,.5,.85,a);}`,
    transparent:true,depthWrite:false
  });

  spdPts=new THREE.Points(g,mat);scene.add(spdPts);
  spdPosArr=pos;
}

function resetSpdP(pos,i){
  const sp=60;
  pos[i*3]=(Math.random()-.5)*sp;
  pos[i*3+1]=Math.random()*40-10;
  pos[i*3+2]=Math.random()*200-50;
}

// ════════════════════════════════════════
// AUDIO — Web Audio API procedural sounds
// ════════════════════════════════════════
let aCtx,engOsc,engGain,masterG,engFilter;

function initAudio(){
  try{
    aCtx=new(window.AudioContext||window.webkitAudioContext)();
    masterG=aCtx.createGain();masterG.gain.value=.25;masterG.connect(aCtx.destination);

    engOsc=aCtx.createOscillator();engOsc.type='sawtooth';engOsc.frequency.value=55;
    engFilter=aCtx.createBiquadFilter();engFilter.type='lowpass';engFilter.frequency.value=180;engFilter.Q.value=4;
    engGain=aCtx.createGain();engGain.gain.value=.12;

    const lfo=aCtx.createOscillator();lfo.frequency.value=6;
    const lg=aCtx.createGain();lg.gain.value=8;lfo.connect(lg);lg.connect(engFilter.frequency);lfo.start();

    engOsc.connect(engFilter);engFilter.connect(engGain);engGain.connect(masterG);engOsc.start();
  }catch(e){}
}

function sfxCollect(){
  if(!aCtx)return;
  const o=aCtx.createOscillator();o.type='sine';
  o.frequency.setValueAtTime(700,aCtx.currentTime);
  o.frequency.exponentialRampToValueAtTime(1400,aCtx.currentTime+.12);
  const g=aCtx.createGain();g.gain.setValueAtTime(.25,aCtx.currentTime);
  g.gain.exponentialRampToValueAtTime(.001,aCtx.currentTime+.18);
  o.connect(g);g.connect(masterG);o.start();o.stop(aCtx.currentTime+.18);
}

function sfxNearMiss(){
  if(!aCtx)return;
  const N=aCtx.sampleRate*.12,buf=aCtx.createBuffer(1,N,aCtx.sampleRate),d=buf.getChannelData(0);
  for(let i=0;i<N;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(N*.08));
  const s=aCtx.createBufferSource();s.buffer=buf;
  const f=aCtx.createBiquadFilter();f.type='bandpass';f.frequency.value=2500;f.Q.value=3;
  const g=aCtx.createGain();g.gain.setValueAtTime(.12,aCtx.currentTime);
  g.gain.exponentialRampToValueAtTime(.001,aCtx.currentTime+.12);
  s.connect(f);f.connect(g);g.connect(masterG);s.start();
}

function sfxCrash(){
  if(!aCtx)return;
  const N=aCtx.sampleRate*.6,buf=aCtx.createBuffer(1,N,aCtx.sampleRate),d=buf.getChannelData(0);
  for(let i=0;i<N;i++) d[i]=(Math.random()*2-1)*Math.exp(-i/(N*.25))*.6;
  const s=aCtx.createBufferSource();s.buffer=buf;
  const f=aCtx.createBiquadFilter();f.type='lowpass';
  f.frequency.setValueAtTime(800,aCtx.currentTime);f.frequency.exponentialRampToValueAtTime(80,aCtx.currentTime+.5);
  const g=aCtx.createGain();g.gain.value=.35;
  s.connect(f);f.connect(g);g.connect(masterG);s.start();
}

function updateEngine(){
  if(!engOsc)return;
  const r=(spd-BASE_SPEED)/(TOP_SPEED*BOOST_MULT-BASE_SPEED);
  engOsc.frequency.value=55+r*130;
  engGain.gain.value=.08+r*.12;
}

// ════════════════════════════════════════
// CAMERA SHAKE
// ════════════════════════════════════════
let camShake=0;
function addShake(a){camShake=Math.min(camShake+a,2.5)}

// ════════════════════════════════════════
// NEAR MISS SYSTEM
// ════════════════════════════════════════
let nmCD=0,nmFlashT=0;

function checkNearMiss(){
  if(nmCD>0)return;
  const pp=phys.pos, closest={d:Infinity};

  for(const ch of chunks){
    const wz=ch.grp.position.z;
    const wd=ch.grp.userData.walls;
    if(wd&&wd.ptsL){
      for(let i=0;i<wd.ptsL.length;i+=2){
        const lp=wd.ptsL[i],rp=wd.ptsR[i];
        const wz2=lp.z+wz;
        if(Math.abs(wz2-pp.z)<18){
          const dl=Math.abs(pp.x-lp.x),dr=Math.abs(pp.x-rp.x);
          const md=Math.min(dl,dr);
          if(md<closest.d) closest.d=md;
        }
      }
    }
    if(ch.grp.userData.obs){
      for(const o of ch.grp.userData.obs){
        const box=new THREE.Box3().setFromObject(o.mesh);
        const d=box.distanceToPoint(pp);
        if(d<closest.d) closest.d=d;
      }
    }
  }

  if(closest.d<NEAR_MISS_DIST&&closest.d>COLLIDE_DIST){
    nmCD=.45;
    score+=~~((NEAR_MISS_DIST-closest.d)*12)*mult;
    mult=Math.min(mult+.5,10);
    turbo=Math.min(turbo+6,100);
    nmFlashT=.35;addShake(.4);sfxNearMiss();

    const el=document.getElementById('multiplier');
    el.textContent='x'+mult.toFixed(1);el.classList.add('active');
    setTimeout(()=>el.classList.remove('active'),350);
  }
}

// ════════════════════════════════════════
// COLLISION DETECTION
// ════════════════════════════════════════
function checkCollisions(){
  const pp=phys.pos, pr=COLLIDE_DIST;

  for(const ch of chunks){
    const wz=ch.grp.position.z;
    const wd=ch.grp.userData.walls;
    if(wd&&wd.ptsL){
      for(let i=0;i<wd.ptsL.length;i+=2){
        const lp=wd.ptsL[i],rp=wd.ptsR[i];
        const wz2=lp.z+wz;
        if(Math.abs(wz2-pp.z)<12){
          if(pp.x<lp.x+pr||pp.x>rp.x-pr) return true;
          if(pp.y>Math.min(lp.y,rp.y)-3||pp.y<-wd.h*.45) return true;
        }
      }
    }

    if(ch.grp.userData.obs){
      for(const o of ch.grp.userData.obs){
        const box=new THREE.Box3().setFromObject(o.mesh);
        box.expandByScalar(pr*.6);
        if(box.containsPoint(pp)) return true;
      }
    }

    // Orb collection
    if(ch.grp.userData.orbs){
      for(const orb of ch.grp.userData.orbs){
        if(!orb.collected){
          const wp=new THREE.Vector3();orb.mesh.getWorldPosition(wp);
          if(pp.distanceTo(wp)<4.5){
            orb.collected=true;orb.mesh.visible=false;
            energy=Math.min(energy+18,100);
            score+=60*mult;turbo=Math.min(turbo+10,100);
            sfxCollect();
          }
        }
      }
    }
  }
  return false;
}

// ════════════════════════════════════════
// GAME INIT / RESET
// ════════════════════════════════════════
function initGame(){
  for(const ch of chunks){scene.remove(ch.grp);ch.grp.traverse(c=>{if(c.geometry)c.geometry.dispose();if(c.material&&!Array.isArray(c.material))c.material.dispose()});}
  chunks=[];chunkIdx=0;

  score=0;mult=1;energy=100;turbo=0;spd=BASE_SPEED;maxSpd=0;distTraveled=0;difficulty=1;
  phys.pos.set(0,5,0);phys.vel.set(0,0,0);phys.roll=0;phys.pitch=0;

  for(let i=0;i<NUM_CHUNKS;i++){
    chunks.push(createChunk(i*CHUNK_LEN-CHUNK_LEN*3));
  }

  if(playerGrp) playerGrp.position.copy(phys.pos);
}

// ════════════════════════════════════════
// HUD UPDATE
// ════════════════════════════════════════
function updateHUD(){
  document.getElementById('score-display').textContent='SCORE: '+Math.floor(score).toLocaleString();
  document.getElementById('speed-fill').style.width=((spd/TOP_SPEED)*100)+'%';
  const ef=document.getElementById('energy-fill');
  ef.style.width=energy+'%';
  ef.style.background=energy<25?'linear-gradient(90deg,#ff3366,#ff0044)':'linear-gradient(90deg,#00d4ff,#7b2ff7)';
  document.getElementById('turbo-fill').style.width=turbo+'%';

  const nm=document.getElementById('near-miss-flash');
  nm.style.opacity=nmFlashT>0?(nmFlashT/.35).toString():'0';

  const bf=document.getElementById('boost-flash');
  bf.style.opacity=(keys.ShiftLeft||keys.ShiftRight)&&turbo>0?'1':'0';
}

// ════════════════════════════════════════
// GAME OVER
// ════════════════════════════════════════
function doGameOver(){
  running=false;sfxCrash();
  document.getElementById('final-score').textContent=Math.floor(score).toLocaleString();
  document.getElementById('max-speed').textContent=(maxSpd*120).toFixed(0)+' km/h';
  document.getElementById('final-distance').textContent=Math.floor(distTraveled/10)+' m';
  document.getElementById('game-over').style.display='flex';
  if(engGain) engGain.gain.value=0;
}

// ════════════════════════════════════════
// MAIN LOOP
// ════════════════════════════════════════
let lastT=performance.now();
const clock=new THREE.Clock();

function loop(){
  requestAnimationFrame(loop);
  const now=performance.now();
  let dt=Math.min((now-lastT)/1000,.05);
  lastT=now;

  if(!running){renderer.render(scene,camera);return;}

  // ── INPUT ──
  const ms=28*dt;
  let ix=0,iy=0;
  if(keys.ArrowLeft||keys.KeyA)ix--;if(keys.ArrowRight||keys.KeyD)ix++;
  if(keys.ArrowUp||keys.KeyW)iy++;if(keys.ArrowDown||keys.KeyS)iy--;

  // Boost
  let boosting=false;
  if((keys.ShiftLeft||keys.ShiftRight)&&turbo>0){boosting=true;turbo-=dt*35;spd=Math.min(spd+dt*6,TOP_SPEED*BOOST_MULT);}
  if(keys.Space) spd=Math.max(spd-dt*10,BASE_SPEED*.4);
  else if(!boosting) spd=Math.min(spd+dt*.4,TOP_SPEED);

  maxSpd=Math.max(maxSpd,spd);

  // ── PHYSICS ──
  phys.targetRoll=-ix*.9;phys.targetPitch=iy*.5;
  const rl=1-Math.pow(.04,dt),pl=1-Math.pow(.07,dt);
  phys.roll+=(phys.targetRoll-phys.roll)*rl;
  phys.pitch+=(phys.targetPitch-phys.pitch)*pl;

  phys.vel.x+=ix*ms*12;phys.vel.y+=iy*ms*6;
  phys.vel.x*=Math.pow(.03,dt);phys.vel.y*=Math.pow(.05,dt);
  phys.vel.x=Math.max(-35,Math.min(35,phys.vel.x));
  phys.vel.y=Math.max(-22,Math.min(22,phys.vel.y));

  const fwd=spd*CHUNK_LEN*dt;
  phys.pos.z+=fwd;phys.pos.x+=phys.vel.x*dt;phys.pos.y+=phys.vel.y*dt;
  phys.pos.y=Math.max(-15,Math.min(40,phys.pos.y));
  distTraveled+=fwd;

  // ── ENERGY ──
  energy-=ENERGY_DECAY*dt*(spd/TOP_SPEED);
  if(energy<=0){doGameOver();return;}

  // ── SCORE & DIFFICULTY ──
  score+=spd*mult*dt*2.5;
  difficulty=1+distTraveled/400;

  // ── NEAR MISS ──
  nmCD-=dt;nmFlashT-=dt;checkNearMiss();

  // ── COLLISIONS ──
  if(checkCollisions()){doGameOver();return;}

  // ── CHUNK MANAGEMENT ──
  const pz=phys.pos.z, front=pz+CHUNK_LEN*6, back=pz-CHUNK_LEN*4;
  chunks=chunks.filter(ch=>{if(ch.grp.position.z<back){scene.remove(ch.grp);ch.grp.traverse(c=>{if(c.geometry)c.geometry.dispose();if(c.material&&!Array.isArray(c.material))c.material.dispose()});return false;}return true;});

  let lastZ=chunks.length?Math.max(...chunks.map(c=>c.grp.position.z)):pz;
  while(lastZ<front){lastZ+=CHUNK_LEN;chunks.push(createChunk(lastZ));}

  // ── PLAYER VISUALS ──
  if(playerGrp){
    playerGrp.position.copy(phys.pos);
    playerGrp.rotation.z=phys.roll;
    playerGrp.rotation.x=-phys.pitch*.5;
    const ei=1+(spd/TOP_SPEED)*4;
    // Engine light is child index varies, find it
    for(const c of playerGrp.children){if(c.isPointLight)c.intensity=ei;}
  }

  // ── CAMERA ──
  const ct=new THREE.Vector3(phys.pos.x*.35,phys.pos.y+5.5,phys.pos.z-16);
  camera.position.lerp(ct,1-Math.pow(.08,dt));

  if(camShake>.01){
    camera.position.x+=(Math.random()-.5)*camShake;
    camera.position.y+=(Math.random()-.5)*camShake*.4;
    camShake*=Math.pow(.92,dt*60);
  }

  const tFOV=78+(spd/TOP_SPEED)*18+(boosting?12:0);
  camera.fov+=(tFOV-camera.fov)*dt*3;camera.updateProjectionMatrix();

  const lt=new THREE.Vector3(phys.pos.x*.5,phys.pos.y+2,phys.pos.z+45);
  camera.lookAt(lt);

  // ── SKY & LIGHTS FOLLOW ──
  sky.position.set(phys.pos.x*.1,phys.pos.y*.1,phys.pos.z);
  sun.position.set(phys.pos.x+30,70,phys.pos.z-80);
  sun.target.position.copy(phys.pos);sun.target.updateMatrixWorld();

  // ── TRAIL PARTICLES ──
  for(let i=0;i<300;i++){
    if(trailAlphaArr[i]>0){trailAlphaArr[i]-=dt*1.8;trailPosArr[i*3+2]-=spd*CHUNK_LEN*dt*.5;}
  }
  if(Math.random()<spd*2.5){
    const idx=trailIdx%300;
    trailPosArr[idx*3]=phys.pos.x+(Math.random()-.5)*1.2;
    trailPosArr[idx*3+1]=phys.pos.y+(Math.random()-.5)*.6;
    trailPosArr[idx*3+2]=phys.pos.z-2.5;
    trailAlphaArr[idx]=.9;trailIdx++;
  }
  trailPts.geometry.attributes.position.needsUpdate=true;
  trailPts.geometry.attributes.alpha.needsUpdate=true;

  // ── SPEED PARTICLES ──
  for(let i=0;i<200;i++){
    spdPosArr[i*3+2]-=spd*CHUNK_LEN*dt*1.8;
    if(spdPosArr[i*3+2]<phys.pos.z-70){resetSpdP(spdPosArr,i);spdPosArr[i*3+2]=phys.pos.z+Math.random()*120;}
  }
  spdPts.geometry.attributes.position.needsUpdate=true;

  // ── ANIMATE ORBS ──
  const t=clock.getElapsedTime();
  for(const ch of chunks){
    if(ch.grp.userData.orbs)for(const o of ch.grp.userData.orbs){
      if(!o.collected){o.mesh.rotation.y=t*2.5;o.mesh.position.y+=Math.sin(t*4+o.mesh.position.x)*dt*.6;}
    }
  }

  // ── AUDIO & HUD ──
  updateEngine();updateHUD();
  renderer.render(scene,camera);
}

// ════════════════════════════════════════
// RESIZE
// ════════════════════════════════════════
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);});

// ════════════════════════════════════════
// START / RESTART
// ════════════════════════════════════════
function startGame(){
  document.getElementById('start-screen').style.display='none';
  document.getElementById('game-over').style.display='none';
  initAudio();buildPlayer();initTrail();initSpeedParticles();initGame();running=true;
}

document.getElementById('start-btn').addEventListener('click',startGame);
document.getElementById('restart-btn').addEventListener('click',()=>{
  if(playerGrp){scene.remove(playerGrp);playerGrp.traverse(c=>{if(c.geometry)c.geometry.dispose();if(c.material&&!Array.isArray(c.material))c.material.dispose()});}
  initAudio();buildPlayer();initGame();
  document.getElementById('game-over').style.display='none';running=true;
});

// Kick off render loop
loop();

})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen3.6-27b-mtp","provider":"lmstudio","persona":"full","sessionId":"371f5420-56ae-4ea0-9c5a-40abe56ae34f","tokensIn":520479,"tokensOut":28102,"tokensTotal":548581,"turns":17,"toolCalls":16,"failedToolCalls":0,"timestamp":"2026-07-17T23:07:15.644Z"} -->