Canyon Run
GLM 5.3 Flash · 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 20,248 bytes · SHA-256 ed0e6ecf5e1e
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>SOLARIS CANYON — Procedural Racer</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#160b24;font-family:"Segoe UI",system-ui,sans-serif;color:#ffe9d6;user-select:none}
#c{position:fixed;inset:0;display:block}
.hud{position:fixed;pointer-events:none;letter-spacing:.12em}
#score{top:22px;left:0;right:0;text-align:center;font-size:30px;font-weight:200;color:#ffd9a0;text-shadow:0 0 18px rgba(255,140,60,.6)}
#score small{display:block;font-size:11px;opacity:.6;letter-spacing:.4em}
#mult{top:64px;left:0;right:0;text-align:center;font-size:52px;font-weight:100;color:#ff9d5c;text-shadow:0 0 24px rgba(255,110,40,.8);transition:transform .12s}
#mult.pulse{transform:scale(1.45);color:#fff3c8}
#speedWrap{bottom:70px;left:50%;transform:translateX(-50%);width:340px}
#speedLine{height:3px;background:linear-gradient(90deg,#ff7a3c,#ffd9a0);width:0%;box-shadow:0 0 12px #ff7a3c;transition:width .1s linear}
#speedTxt{font-size:11px;opacity:.75;margin-top:6px;text-align:center}
#energyWrap{bottom:34px;left:50%;transform:translateX(-50%);width:340px;height:8px;border:1px solid rgba(255,180,120,.5);border-radius:6px;overflow:hidden}
#energy{height:100%;width:100%;background:linear-gradient(90deg,#ff5e3a,#ffc46b);box-shadow:0 0 14px #ff6a3a}
#turboWrap{bottom:22px;left:50%;transform:translateX(-50%);width:340px;height:4px;overflow:hidden;opacity:.9}
#turbo{height:100%;width:0%;background:#8fd8ff;box-shadow:0 0 10px #8fd8ff}
#warn{top:46%;left:0;right:0;text-align:center;font-size:22px;color:#ff4d4d;opacity:0;letter-spacing:.5em}
#overlay{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(20,8,32,.82);backdrop-filter:blur(6px);text-align:center}
#overlay h1{font-weight:100;font-size:54px;letter-spacing:.3em;color:#ffd9a0;text-shadow:0 0 30px #ff7a3c;margin:0 0 8px}
#overlay .stats{font-size:16px;letter-spacing:.25em;opacity:.85;line-height:2}
#overlay button{margin-top:28px;pointer-events:auto;background:none;border:1px solid #ff9d5c;color:#ffd9a0;font-size:15px;letter-spacing:.35em;padding:12px 42px;cursor:pointer;transition:.2s}
#overlay button:hover{background:#ff9d5c;color:#1a0d2a;box-shadow:0 0 30px #ff7a3c}
#startHint{bottom:120px;left:0;right:0;text-align:center;font-size:13px;opacity:.8;letter-spacing:.2em;line-height:2}
.hidden{display:none!important}
</style>
<script type="importmap">
{"imports":{"three":"https://unpkg.com/three@0.160.0/build/three.module.js","three/addons/":"https://unpkg.com/three@0.160.0/examples/jsm/"}}
</script>
</head>
<body>
<canvas id="c"></canvas>
<div class="hud" id="score">0<small>SCORE</small></div>
<div class="hud" id="mult">×1</div>
<div class="hud" id="speedWrap"><div id="speedLine"></div><div id="speedTxt">0 KM/H</div></div>
<div class="hud" id="energyWrap"><div id="energy"></div></div>
<div class="hud" id="turboWrap"><div id="turbo"></div></div>
<div class="hud" id="warn">ENERGY CRITICAL</div>
<div class="hud" id="startHint">A / D — BANK · W — DIVE (ACCELERATE) · S — CLIMB<br>SHIFT — TURBO · GRAZE WALLS FOR MULTIPLIER · COLLECT ORBS FOR ENERGY<br><br>PRESS ANY KEY TO LAUNCH</div>
<div id="overlay" class="hidden">
<h1>SIGNAL LOST</h1>
<div class="stats" id="stats"></div>
<button id="restart">RESTART</button>
</div>
<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';
import {AfterimagePass} from 'three/addons/postprocessing/AfterimagePass.js';
import {OutputPass} from 'three/addons/postprocessing/OutputPass.js';
/* ---------- renderer / scene ---------- */
const canvas=document.getElementById('c');
const renderer=new THREE.WebGLRenderer({canvas,antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,1.75));
renderer.setSize(innerWidth,innerHeight);
renderer.shadowMap.enabled=true;
renderer.shadowMap.type=THREE.PCFSoftShadowMap;
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x1c0f30);
scene.fog=new THREE.Fog(0x2a1544,120,760);
const camera=new THREE.PerspectiveCamera(75,innerWidth/innerHeight,0.1,2000);
const hemi=new THREE.HemisphereLight(0x7a4fd0,0x3a1c10,0.85); scene.add(hemi);
const sun=new THREE.DirectionalLight(0xffa050,2.2);
sun.castShadow=true;
sun.shadow.mapSize.set(1024,1024);
sun.shadow.camera.left=-120;sun.shadow.camera.right=120;
sun.shadow.camera.top=120;sun.shadow.camera.bottom=-120;
sun.shadow.camera.far=500;sun.shadow.bias=-0.0015;
scene.add(sun,sun.target);
/* sun disc */
const sunDisc=new THREE.Mesh(new THREE.CircleGeometry(90,32),
new THREE.MeshBasicMaterial({color:0xffb060,fog:false}));
scene.add(sunDisc);
/* ground */
const ground=new THREE.Mesh(new THREE.PlaneGeometry(600,2400),
new THREE.MeshLambertMaterial({color:0x2b1533}));
ground.rotation.x=-Math.PI/2; ground.receiveShadow=true; scene.add(ground);
/* ---------- composer ---------- */
const composer=new EffectComposer(renderer);
composer.addPass(new RenderPass(scene,camera));
const bloom=new UnrealBloomPass(new THREE.Vector2(innerWidth,innerHeight),0.9,0.55,0.6);
composer.addPass(bloom);
const after=new AfterimagePass(0.72);
composer.addPass(after);
composer.addPass(new OutputPass());
/* ---------- craft ---------- */
const craft=new THREE.Group();
{
const bodyMat=new THREE.MeshStandardMaterial({color:0xd8dced,flatShading:true,metalness:.65,roughness:.3});
const darkMat=new THREE.MeshStandardMaterial({color:0x3a2a55,flatShading:true,metalness:.5,roughness:.5});
const body=new THREE.Mesh(new THREE.ConeGeometry(1.15,4.4,6),bodyMat);
body.geometry.rotateX(Math.PI/2); body.castShadow=true; craft.add(body);
const wing=new THREE.BoxGeometry(4.6,.18,1.6);
const w=new THREE.Mesh(wing,bodyMat); w.position.set(0,-.1,-.6); w.castShadow=true; craft.add(w);
const fin=new THREE.Mesh(new THREE.BoxGeometry(.16,1.3,1.4),darkMat);
fin.position.set(0,.7,-1.6); craft.add(fin);
const glowMat=new THREE.MeshBasicMaterial({color:0x8fd8ff});
const eg=new THREE.Mesh(new THREE.SphereGeometry(.45,10,10),glowMat);
eg.position.set(0,0,-2.1); craft.add(eg);
const eg2=new THREE.Mesh(new THREE.SphereGeometry(.22,8,8),glowMat);
eg2.position.set(0,.75,-2.2); craft.add(eg2);
}
scene.add(craft);
/* ---------- shared geometry / materials ---------- */
const unitBox=new THREE.BoxGeometry(1,1,1);
const wallMat=new THREE.MeshStandardMaterial({color:0x5a3a6e,flatShading:true,roughness:.9,metalness:.05});
const monoMat=new THREE.MeshStandardMaterial({color:0x8a6aa0,flatShading:true,roughness:.7,metalness:.15,emissive:0x2a1030});
const orbGeo=new THREE.IcosahedronGeometry(1.1,0);
const orbMat=new THREE.MeshBasicMaterial({color:0xffb040});
/* ---------- dust particles ---------- */
const DUST=420;
const dustPos=new Float32Array(DUST*3);
for(let i=0;i<DUST;i++){dustPos[i*3]=(Math.random()-.5)*160;dustPos[i*3+1]=Math.random()*45;dustPos[i*3+2]=Math.random()*500;}
const dustGeo=new THREE.BufferGeometry();
dustGeo.setAttribute('position',new THREE.BufferAttribute(dustPos,3));
const dust=new THREE.Points(dustGeo,new THREE.PointsMaterial({color:0xffa070,size:.55,transparent:true,opacity:.65,sizeAttenuation:true}));
scene.add(dust);
/* ---------- trail pool ---------- */
const trailPool=[];
const trailMat=new THREE.MeshBasicMaterial({color:0xff8a3c,transparent:true,blending:THREE.AdditiveBlending,depthWrite:false});
for(let i=0;i<50;i++){
const m=new THREE.Mesh(new THREE.SphereGeometry(.32,6,6),trailMat.clone());
m.visible=false; scene.add(m); trailPool.push({m,life:0});
}
let trailIdx=0;
function spawnTrail(x,y,z){
const t=trailPool[trailIdx++%trailPool.length];
t.m.position.set(x,y,z); t.life=1; t.m.visible=true;
}
/* ---------- audio ---------- */
let AC=null,engineOsc,engineGain,engineFilter,noiseBuf;
function initAudio(){
if(AC)return;
AC=new (window.AudioContext||window.webkitAudioContext)();
engineOsc=AC.createOscillator(); engineOsc.type='sawtooth'; engineOsc.frequency.value=70;
engineFilter=AC.createBiquadFilter(); engineFilter.type='lowpass'; engineFilter.frequency.value=400;
engineGain=AC.createGain(); engineGain.gain.value=0;
engineOsc.connect(engineFilter).connect(engineGain).connect(AC.destination);
engineOsc.start();
const len=AC.sampleRate*1.2; noiseBuf=AC.createBuffer(1,len,AC.sampleRate);
const d=noiseBuf.getChannelData(0); for(let i=0;i<len;i++)d[i]=Math.random()*2-1;
}
function ping(){
if(!AC)return; const t=AC.currentTime;
const o=AC.createOscillator(),g=AC.createGain();
o.type='sine'; o.frequency.setValueAtTime(880,t); o.frequency.exponentialRampToValueAtTime(1560,t+.09);
g.gain.setValueAtTime(.18,t); g.gain.exponentialRampToValueAtTime(.001,t+.25);
o.connect(g).connect(AC.destination); o.start(t); o.stop(t+.26);
}
function whoosh(){
if(!AC)return; const t=AC.currentTime;
const s=AC.createBufferSource(); s.buffer=noiseBuf;
const f=AC.createBiquadFilter(); f.type='bandpass'; f.Q.value=1.4;
f.frequency.setValueAtTime(300,t); f.frequency.exponentialRampToValueAtTime(2400,t+.28);
const g=AC.createGain(); g.gain.setValueAtTime(.0001,t);
g.gain.exponentialRampToValueAtTime(.3,t+.06); g.gain.exponentialRampToValueAtTime(.001,t+.45);
s.connect(f).connect(g).connect(AC.destination); s.start(t); s.stop(t+.5);
}
function boom(){
if(!AC)return; const t=AC.currentTime;
const s=AC.createBufferSource(); s.buffer=noiseBuf;
const f=AC.createBiquadFilter(); f.type='lowpass';
f.frequency.setValueAtTime(900,t); f.frequency.exponentialRampToValueAtTime(60,t+.8);
const g=AC.createGain(); g.gain.setValueAtTime(.5,t); g.gain.exponentialRampToValueAtTime(.001,t+.9);
s.connect(f).connect(g).connect(AC.destination); s.start(t); s.stop(t+.95);
}
/* ---------- game state ---------- */
const CHUNK_LEN=220, SEG=8, SEG_LEN=CHUNK_LEN/SEG;
let chunks=[], genZ=0, genX=0, genHalf=45;
const keys={};
let mode='ready'; // ready | play | over
let S=null;
const elScore=document.getElementById('score'),elMult=document.getElementById('mult'),
elSpeed=document.getElementById('speedLine'),elSpeedTxt=document.getElementById('speedTxt'),
elEnergy=document.getElementById('energy'),elTurbo=document.getElementById('turbo'),
elWarn=document.getElementById('warn'),elStats=document.getElementById('stats');
function reset(){
for(const c of chunks) scene.remove(c.group);
chunks=[]; genZ=40; genX=0; genHalf=45;
S={x:0,z:40,vx:0,speed:70,base:70,energy:100,turbo:0,boost:false,
score:0,mult:1,multTimer:0,nearCool:0,shake:0,dist:0,maxSpeed:0,
pitch:0,bank:0,fov:75,time:0};
craft.position.set(0,7,40);
updateHUD();
document.getElementById('overlay').classList.add('hidden');
document.getElementById('warn').style.opacity=0;
}
reset();
mode='ready';
/* ---------- chunk generation ---------- */
function addBox(group,obstacles,cx,cy,cz,sx,sy,sz,mat){
const m=new THREE.Mesh(unitBox,mat);
m.position.set(cx,cy,cz); m.scale.set(sx,sy,sz);
m.castShadow=true; m.receiveShadow=true; group.add(m);
obstacles.push({minX:cx-sx/2,maxX:cx+sx/2,minZ:cz-sz/2,maxZ:cz+sz/2,passed:false});
return m;
}
function makeChunk(){
const diff=Math.min(1,S.dist/7000);
const group=new THREE.Group();
const obstacles=[],orbs=[];
const z0=genZ;
const targetHalf=Math.max(16-diff*4, 22+Math.random()*30);
const targetX=Math.max(-90,Math.min(90, genX+(Math.random()-.5)*70));
const squeeze=Math.random()<0.22+diff*0.2;
let cx=genX, half=genHalf;
const monoCount=Math.floor(1+diff*3+Math.random()*2);
const monoSpots=[];
for(let i=0;i<SEG;i++){
const t=(i+1)/SEG, sm=t*t*(3-2*t);
let h=half+(targetHalf-half)*sm;
let x=cx+(targetX-cx)*sm;
if(squeeze&&i>=2&&i<=5) h=Math.min(h,12+Math.random()*3);
const z=z0+i*SEG_LEN+SEG_LEN/2;
const hL=26+Math.random()*38, hR=26+Math.random()*38;
addBox(group,obstacles,x-h-16,hL/2,z,32,hL,SEG_LEN+1,wallMat);
addBox(group,obstacles,x+h+16,hR/2,z,32,hR,SEG_LEN+1,wallMat);
if(i>=1&&i<=SEG-2) monoSpots.push({x,h,z});
cx=x; half=h;
}
// monoliths
for(let i=0;i<monoCount;i++){
const s=monoSpots[Math.floor(Math.random()*monoSpots.length)];
if(!s)continue;
const w=3+Math.random()*5,d=3+Math.random()*5,ht=16+Math.random()*26;
const mx=s.x+(Math.random()-.5)*2*(s.h-6);
addBox(group,obstacles,mx,ht/2,s.z,w,ht,d,monoMat);
}
// orbs
const orbCount=3+Math.floor(Math.random()*3);
let oz=z0+20;
for(let i=0;i<orbCount;i++){
oz+=CHUNK_LEN/orbCount*(0.7+Math.random()*.6);
const segIdx=Math.min(SEG-1,Math.floor((oz-z0)/SEG_LEN));
const t=(segIdx+1)/SEG, sm=t*t*(3-2*t);
const ox=cx+(targetX-cx)*sm;
const m=new THREE.Mesh(orbGeo,orbMat);
m.position.set(ox+(Math.random()-.5)*Math.min(20,cx?20:20),7,oz);
group.add(m);
orbs.push({mesh:m,x:m.position.x,z:oz,taken:false,phase:Math.random()*7});
}
scene.add(group);
chunks.push({group,obstacles,orbs,z0,z1:z0+CHUNK_LEN});
genZ=z0+CHUNK_LEN; genX=cx; genHalf=half;
}
/* ---------- input ---------- */
addEventListener('keydown',e=>{
keys[e.code]=true; initAudio(); if(AC&&AC.state==='suspended')AC.resume();
if(mode==='ready'){mode='play';document.getElementById('startHint').classList.add('hidden');}
else if(mode==='over'&&(e.code==='KeyR'||e.code==='Enter')){reset();mode='play';}
if(e.code==='Space')e.preventDefault();
});
addEventListener('keyup',e=>keys[e.code]=false);
canvas.addEventListener('pointerdown',()=>{
initAudio(); if(AC&&AC.state==='suspended')AC.resume();
if(mode==='ready'){mode='play';document.getElementById('startHint').classList.add('hidden');}
});
document.getElementById('restart').addEventListener('click',()=>{reset();mode='play';});
addEventListener('resize',()=>{
camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight); composer.setSize(innerWidth,innerHeight);
});
/* ---------- HUD ---------- */
function updateHUD(){
elScore.firstChild.textContent=Math.floor(S.score).toLocaleString();
elMult.textContent='×'+S.mult;
elSpeed.style.width=Math.min(100,(S.speed/260)*100)+'%';
elSpeedTxt.textContent=Math.round(S.speed*2.4)+' KM/H'+(S.boost?' ▲ TURBO':'');
elEnergy.style.width=Math.max(0,S.energy)+'%';
elTurbo.style.width=S.turbo+'%';
}
/* ---------- game over ---------- */
function gameOver(){
mode='over'; boom();
if(engineGain)engineGain.gain.value=0;
elStats.innerHTML='FINAL SCORE — <b style="color:#ffd9a0;font-size:26px">'+Math.floor(S.score).toLocaleString()+'</b><br>MAX SPEED — '+Math.round(S.maxSpeed*2.4)+' KM/H<br>DISTANCE — '+Math.round(S.dist)+' M';
document.getElementById('overlay').classList.remove('hidden');
}
/* ---------- main loop ---------- */
let last=performance.now();
function frame(now){
requestAnimationFrame(frame);
window.__g=()=>({mode,S,chunks:chunks.length,obstacles:chunks.reduce((a,c)=>a+c.obstacles.length,0),orbs:chunks.reduce((a,c)=>a+c.orbs.length,0),genZ});
const dt=Math.min(0.05,(now-last)/1000); last=now;
S.time+=dt;
if(mode==='play'){
const diff=Math.min(1,S.dist/7000);
S.base=70+65*diff;
// steering
const steer=(keys.KeyD||keys.ArrowRight?1:0)-(keys.KeyA||keys.ArrowLeft?1:0);
S.vx+=steer*95*dt;
S.vx*=Math.pow(0.02,dt);
S.vx=Math.max(-70,Math.min(70,S.vx));
S.x+=S.vx*dt;
// pitch-based accel
const dive=(keys.KeyW||keys.ArrowUp?1:0)-(keys.KeyS||keys.ArrowDown?1:0);
S.pitch+=((dive*0.55)-S.pitch)*Math.min(1,dt*6);
// turbo
S.boost=(keys.ShiftLeft||keys.ShiftRight)&&S.turbo>2;
if(S.boost)S.turbo=Math.max(0,S.turbo-32*dt);
const target=S.base*(S.boost?1.65:1)+dive*30;
S.speed+=(target-S.speed)*Math.min(1,dt*(S.boost?2.2:0.9));
S.maxSpeed=Math.max(S.maxSpeed,S.speed);
S.z+=S.speed*dt;
S.dist+=S.speed*dt;
// energy
S.energy-=(4.5+S.speed*0.055)*dt;
elWarn.style.opacity=S.energy<25?(0.5+0.5*Math.sin(S.time*10)):0;
if(S.energy<=0){S.energy=0;gameOver();}
// multiplier decay
S.multTimer-=dt;
if(S.multTimer<=0&&S.mult>1){S.mult=1;updateHUD();}
S.nearCool-=dt;
// generation / cleanup
while(genZ<S.z+900)makeChunk();
for(let i=chunks.length-1;i>=0;i--){
if(chunks[i].z1<S.z-160){
scene.remove(chunks[i].group);
chunks[i].group.traverse(o=>{if(o.isMesh&&o.geometry!==unitBox&&o.geometry!==orbGeo)o.geometry.dispose();});
chunks.splice(i,1);
}
}
// collisions + near miss
const r=2.1;
outer:
for(const c of chunks){
if(c.z1<S.z-40||c.z0>S.z+80)continue;
for(const o of c.obstacles){
if(S.x>o.minX-r&&S.x<o.maxX+r&&S.z>o.minZ-r&&S.z<o.maxZ+r){gameOver();break outer;}
if(!o.passed&&S.z>o.maxZ+r){
o.passed=true;
const clear=Math.max(o.minX-S.x,S.x-o.maxX);
if(clear<5.5&&clear>=0&&S.nearCool<=0){
S.nearCool=0.25;
S.mult=Math.min(10,S.mult+1);
S.multTimer=4;
S.score+=100*S.mult;
S.turbo=Math.min(100,S.turbo+10);
S.shake=Math.min(1,S.shake+0.55);
whoosh();
elMult.classList.add('pulse');
setTimeout(()=>elMult.classList.remove('pulse'),130);
updateHUD();
}
}
}
for(const ob of c.orbs){
if(ob.taken)continue;
const dx=ob.x-S.x,dz=ob.z-S.z;
if(dx*dx+dz*dz<36){
ob.taken=true; ob.mesh.visible=false;
S.energy=Math.min(100,S.energy+30);
S.score+=50*S.mult;
S.turbo=Math.min(100,S.turbo+8);
ping(); updateHUD();
}
}
}
// score over time
S.score+=S.speed*dt*0.35*S.mult;
if(Math.floor(S.time*10)%2===0)updateHUD();
}
// craft transform
craft.position.set(S.x,7+Math.sin(S.time*2.2)*0.35,S.z);
S.bank+=((-S.vx*0.012)-S.bank)*Math.min(1,dt*8);
craft.rotation.z=S.bank;
craft.rotation.x=-S.pitch*0.5;
craft.rotation.y=-S.bank*0.35;
// trail
if(mode==='play'){
const n=S.boost?3:1;
for(let i=0;i<n;i++)
spawnTrail(S.x+(Math.random()-.5)*1.2-S.bank*2,6.9+(Math.random()-.5)*.5,S.z-2.3-Math.random()*1.5);
}
for(const t of trailPool){
if(t.life<=0)continue;
t.life-=dt*2.4;
if(t.life<=0){t.m.visible=false;continue;}
t.m.scale.setScalar(0.12+t.life*0.45);
t.m.material.opacity=t.life*0.5;
}
// dust recycle
const p=dustGeo.attributes.position.array;
for(let i=0;i<DUST;i++){
if(p[i*3+2]<S.z-30){p[i*3+2]=S.z+300+Math.random()*250;p[i*3]=(Math.random()-.5)*160;p[i*3+1]=Math.random()*45;}
}
dustGeo.attributes.position.needsUpdate=true;
// camera
S.shake=Math.max(0,S.shake-dt*1.8);
const sh=S.shake*0.6+(S.boost?0.12:0)+S.speed*0.0006;
const camX=S.x*0.85+S.bank*3;
camera.position.x+=(camX-camera.position.x)*Math.min(1,dt*4);
camera.position.y+=(11-camera.position.y)*Math.min(1,dt*4);
camera.position.z=S.z-14;
camera.position.x+=(Math.random()-.5)*sh;
camera.position.y+=(Math.random()-.5)*sh;
camera.lookAt(S.x*0.92,6.5,S.z+30);
const targetFov=S.boost?95:75+S.speed*0.06;
S.fov+=(targetFov-S.fov)*Math.min(1,dt*4);
camera.fov=S.fov; camera.updateProjectionMatrix();
after.uniforms['damp'].value=S.boost?0.85:0.68;
// sun / shadows follow
sun.position.set(S.x+90,70,S.z+50);
sun.target.position.set(S.x,0,S.z+60);
sunDisc.position.set(S.x-260,110,S.z+900);
ground.position.z=S.z+600;
// engine audio
if(AC&&engineGain){
engineGain.gain.value=mode==='play'?0.035+S.speed*0.00035:0;
engineOsc.frequency.value=55+S.speed*0.7+(S.boost?40:0);
engineFilter.frequency.value=300+S.speed*6;
}
// orb animation
for(const c of chunks)for(const ob of c.orbs){
if(ob.taken)continue;
ob.mesh.position.y=7+Math.sin(S.time*2.5+ob.phase)*0.9;
ob.mesh.rotation.y+=dt*2; ob.mesh.rotation.x+=dt;
}
composer.render();
}
requestAnimationFrame(frame);
</script>
</body>
</html>