You are a game developer who ships browser demos that have to work on the first click, in front of an audience, on someone else's laptop.
Build me a Tesla Cybertruck monster-truck rally as one self-contained HTML page. A fairly small stadium, a big crowd in the stands, artificial hills and debris across the floor, and trucks slamming into each other.
Then make it match the spec below exactly. The spec is the challenge; a great-looking rally that ignores it is a miss.
.html page. No build step. Saved to disk and double-clicked, it runs.localStorage, sessionStorage, cookies or indexedDB. It runs in a sandboxed frame where touching them throws.Both are checkable in ten seconds, which is why they are here.
#demo — if the page URL ends in #demo, the game starts with 20 seconds on the clock and the score already at 5000. This must run through the real game state, not a cosmetic overlay. Everything else behaves normally.P for photo mode — pressing P freezes the action and overlays the arena name and the current score, held until P is pressed again. The simulation stops; it does not keep playing behind the card.One sentence: the name of your rally and what it looks like.
Exactly one fenced html block containing the whole page and nothing else.
Exactly four bullets.
#demo sets, and why it is not an overlay.P is pressed — the render loop, the physics step, or both.Cyber Dust Colosseum is a neon-lit stadium monster-truck rally where five angular Cybertrucks smash over dirt hills and debris in front of a bouncing crowd all the way round.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Cyber Dust Colosseum - Cybertruck Monster Rally</title>
<link rel="icon" href="data:,">
<style>
html,body{margin:0;padding:0;overflow:hidden;background:#0b1020;font-family:Arial,Helvetica,sans-serif;color:#fff}
#hud{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:10px 16px;background:rgba(0,0,0,.55);z-index:10;box-sizing:border-box}
#arena{font-size:22px;font-weight:900;letter-spacing:1px;color:#ffe14d}
#mid{text-align:center;font-size:18px;font-weight:700}
#score{color:#7cfc9a}
#hint{position:fixed;left:50%;transform:translateX(-50%);bottom:10px;background:rgba(0,0,0,.55);padding:6px 12px;border-radius:20px;font-size:13px;z-index:10;white-space:nowrap}
#msg{position:fixed;left:50%;top:22%;transform:translateX(-50%);font-size:28px;font-weight:900;color:#ffe14d;text-shadow:2px 2px 0 #000;opacity:0;z-index:11;pointer-events:none}
#photo{position:fixed;inset:0;background:rgba(5,8,20,.82);display:none;flex-direction:column;align-items:center;justify-content:center;z-index:20;text-align:center}
#photo.show{display:flex}
#photo .pArena{font-size:42px;font-weight:900;color:#ffe14d;letter-spacing:2px}
#photo .pScore{font-size:34px;margin-top:14px}
#photo .pHint{margin-top:20px;font-size:14px;opacity:.8}
canvas{display:block}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<div id="hud"><div id="arena">CYBER DUST COLOSSEUM</div><div id="mid"><span id="score">SCORE: 0</span> <span id="clock">TIME: 90</span></div><div id="drive">WASD / ARROWS</div></div>
<div id="msg"></div>
<div id="hint">Drive: WASD or Arrow Keys | P = Photo Mode | Ram rivals to score!</div>
<div id="photo"><div class="pArena">CYBER DUST COLOSSEUM</div><div class="pScore" id="photoScore">SCORE: 0</div><div class="pHint">PHOTO MODE - Press P to resume</div></div>
<script>
(function(){
'use strict';
var ARENA_NAME='CYBER DUST COLOSSEUM';
var timeLeft=90;
var score=0;
if (window.location.hash === '#demo') { timeLeft = 20; score = 5000; }
var photoMode=false;
var shake=0;
var msgTimer=0;
var scoreEl=document.getElementById('score');
var clockEl=document.getElementById('clock');
var msgEl=document.getElementById('msg');
var photoEl=document.getElementById('photo');
var photoScoreEl=document.getElementById('photoScore');
function showMsg(t){msgEl.textContent=t;msgEl.style.opacity='1';msgTimer=0.9;}
var renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled=true;
document.body.appendChild(renderer.domElement);
var scene=new THREE.Scene();
scene.background=new THREE.Color(0x87b5d6);
scene.fog=new THREE.Fog(0x87b5d6,90,180);
var camera=new THREE.PerspectiveCamera(62,window.innerWidth/window.innerHeight,0.1,300);
camera.position.set(0,10,34);
var hemi=new THREE.HemisphereLight(0xbfdfff,0x554433,0.75);scene.add(hemi);
var sun=new THREE.DirectionalLight(0xffffff,0.95);sun.position.set(30,45,20);sun.castShadow=true;sun.shadow.camera.left=-55;sun.shadow.camera.right=55;sun.shadow.camera.top=55;sun.shadow.camera.bottom=-55;sun.shadow.mapSize.width=1024;sun.shadow.mapSize.height=1024;scene.add(sun);
var HILLS=[{x:-20,z:-10,h:2.4,s:6},{x:18,z:-12,h:2.6,s:7},{x:0,z:12,h:2.0,s:6},{x:-16,z:14,h:1.9,s:5},{x:22,z:10,h:2.1,s:6},{x:2,z:-2,h:1.4,s:5}];
function groundH(x,z){var h=0;for(var i=0;i<HILLS.length;i++){var hill=HILLS[i];var dx=x-hill.x,dz=z-hill.z;h+=hill.h*Math.exp(-(dx*dx+dz*dz)/(2*hill.s*hill.s));}return h;}
var floorGeo=new THREE.PlaneGeometry(84,64,84,64);floorGeo.rotateX(-Math.PI/2);
var fp=floorGeo.attributes.position;for(var fi=0;fi<fp.count;fi++){fp.setY(fi,groundH(fp.getX(fi),fp.getZ(fi)));}
floorGeo.computeVertexNormals();
var floor=new THREE.Mesh(floorGeo,new THREE.MeshStandardMaterial({color:0x8f6f47,roughness:1}));floor.receiveShadow=true;scene.add(floor);
var ring=new THREE.Mesh(new THREE.RingGeometry(6,7,32),new THREE.MeshBasicMaterial({color:0xffe14d,transparent:true,opacity:0.55,side:THREE.DoubleSide}));ring.rotation.x=-Math.PI/2;ring.position.y=0.06;scene.add(ring);
var wallMat=new THREE.MeshStandardMaterial({color:0x1f2937,roughness:0.9});
function addWall(w,d,x,z){var m=new THREE.Mesh(new THREE.BoxGeometry(w,2.6,d),wallMat);m.position.set(x,1.3,z);m.castShadow=true;scene.add(m);var s=new THREE.Mesh(new THREE.BoxGeometry(w===2?0.2:w,0.15,d===2?0.2:d),new THREE.MeshBasicMaterial({color:0x00e5ff}));s.position.set(x,2.7,z);scene.add(s);}
addWall(82,2,0,-29);addWall(82,2,0,29);addWall(2,60,-39,0);addWall(2,60,39,0);
var standMat=new THREE.MeshStandardMaterial({color:0x33415c,roughness:1});
var tiers=[];
for(var tier=0;tier<6;tier++){var th=1.2+tier*0.95;var zOff=31+tier*1.7;var xOff=41+tier*1.7;tiers.push({th:th,zOff:zOff,xOff:xOff});
var n=new THREE.Mesh(new THREE.BoxGeometry(86,th,2.4),standMat);n.position.set(0,th/2,-zOff);scene.add(n);
var s2=new THREE.Mesh(new THREE.BoxGeometry(86,th,2.4),standMat);s2.position.set(0,th/2,zOff);scene.add(s2);
var w=new THREE.Mesh(new THREE.BoxGeometry(2.4,th,68),standMat);w.position.set(-xOff,th/2,0);scene.add(w);
var e=new THREE.Mesh(new THREE.BoxGeometry(2.4,th,68),standMat);e.position.set(xOff,th/2,0);scene.add(e);}
var crowdData=[];
for(var ti=0;ti<tiers.length;ti++){var T=tiers[ti];
for(var cx=-38;cx<=38;cx+=1.05){crowdData.push({x:cx+(Math.random()-0.5)*0.3,y:T.th+0.4,z:-T.zOff,phase:Math.random()*6.28,rot:0});crowdData.push({x:cx+(Math.random()-0.5)*0.3,y:T.th+0.4,z:T.zOff,phase:Math.random()*6.28,rot:Math.PI});}
for(var cz=-28;cz<=28;cz+=1.05){crowdData.push({x:-T.xOff,y:T.th+0.4,z:cz+(Math.random()-0.5)*0.3,phase:Math.random()*6.28,rot:Math.PI/2});crowdData.push({x:T.xOff,y:T.th+0.4,z:cz+(Math.random()-0.5)*0.3,phase:Math.random()*6.28,rot:-Math.PI/2});}}
var crowdCount=crowdData.length;
var bodyMesh=new THREE.InstancedMesh(new THREE.BoxGeometry(0.55,0.75,0.35),new THREE.MeshLambertMaterial({color:0xffffff}),crowdCount);
var headMesh=new THREE.InstancedMesh(new THREE.SphereGeometry(0.2,6,5),new THREE.MeshLambertMaterial({color:0xffffff}),crowdCount);
var dummy=new THREE.Object3D();var tmpColor=new THREE.Color();
for(var ci=0;ci<crowdCount;ci++){var cd=crowdData[ci];dummy.position.set(cd.x,cd.y,cd.z);dummy.rotation.set(0,cd.rot,0);dummy.updateMatrix();bodyMesh.setMatrixAt(ci,dummy.matrix);tmpColor.setHSL(Math.random(),0.75,0.55);bodyMesh.setColorAt(ci,tmpColor);dummy.position.y+=0.55;dummy.updateMatrix();headMesh.setMatrixAt(ci,dummy.matrix);tmpColor.setHSL(0.08,0.5,0.55+Math.random()*0.3);headMesh.setColorAt(ci,tmpColor);}
bodyMesh.instanceMatrix.needsUpdate=true;headMesh.instanceMatrix.needsUpdate=true;if(bodyMesh.instanceColor)bodyMesh.instanceColor.needsUpdate=true;if(headMesh.instanceColor)headMesh.instanceColor.needsUpdate=true;scene.add(bodyMesh);scene.add(headMesh);
function updateCrowd(t){for(var i=0;i<crowdCount;i++){var c=crowdData[i];var bob=Math.abs(Math.sin(t*3+c.phase))*0.22;dummy.position.set(c.x,c.y+bob,c.z);dummy.rotation.set(0,c.rot,0);dummy.updateMatrix();bodyMesh.setMatrixAt(i,dummy.matrix);dummy.position.y+=0.55;dummy.updateMatrix();headMesh.setMatrixAt(i,dummy.matrix);}bodyMesh.instanceMatrix.needsUpdate=true;headMesh.instanceMatrix.needsUpdate=true;}
var poleMat=new THREE.MeshStandardMaterial({color:0x666666});
[[-36,-27],[36,-27],[-36,27],[36,27]].forEach(function(p){var pole=new THREE.Mesh(new THREE.CylinderGeometry(0.15,0.15,9,8),poleMat);pole.position.set(p[0],4.5,p[1]);scene.add(pole);var head=new THREE.Mesh(new THREE.BoxGeometry(1.6,0.8,0.3),new THREE.MeshBasicMaterial({color:0xfff8b0}));head.position.set(p[0],9.2,p[1]);scene.add(head);});
var obstacles=[];
var barrelPos=[[-10,-5],[12,4],[-25,2],[8,-18],[25,-4],[-5,20],[15,18],[-28,-18],[28,16],[0,-22]];
barrelPos.forEach(function(p){var m=new THREE.Mesh(new THREE.CylinderGeometry(0.65,0.65,1.4,12),new THREE.MeshStandardMaterial({color:0xd62828,roughness:0.6}));m.position.set(p[0],0.7+groundH(p[0],p[1]),p[1]);m.castShadow=true;scene.add(m);obstacles.push({x:p[0],z:p[1],r:0.85,mesh:m,type:'barrel',tipped:false});});
var cratePos=[[-14,8],[20,-8],[5,5],[-8,-14],[26,2],[-24,-6],[10,22],[-18,20]];
cratePos.forEach(function(p){var m=new THREE.Mesh(new THREE.BoxGeometry(1.6,1.6,1.6),new THREE.MeshStandardMaterial({color:0xb08954,roughness:0.9}));m.position.set(p[0],0.8+groundH(p[0],p[1]),p[1]);m.rotation.y=Math.random();m.castShadow=true;scene.add(m);obstacles.push({x:p[0],z:p[1],r:1.15,mesh:m,type:'crate',tipped:false});});
var tirePos=[[-2,16],[16,-16],[-22,-20],[24,20],[6,-8],[-12,2]];
tirePos.forEach(function(p){var g=new THREE.Group();for(var k=0;k<3;k++){var tm=new THREE.Mesh(new THREE.CylinderGeometry(1,1,0.45,14),new THREE.MeshStandardMaterial({color:0x111111,roughness:0.95}));tm.position.y=0.25+k*0.5;tm.castShadow=true;g.add(tm);}g.position.set(p[0],groundH(p[0],p[1]),p[1]);scene.add(g);obstacles.push({x:p[0],z:p[1],r:1.1,mesh:g,type:'tire',tipped:false});});
var smokes=[];
for(var si=0;si<50;si++){var sm=new THREE.Mesh(new THREE.BoxGeometry(0.5,0.5,0.5),new THREE.MeshBasicMaterial({color:0x888888,transparent:true,opacity:0}));sm.visible=false;scene.add(sm);smokes.push({mesh:sm,life:0,vel:new THREE.Vector3()});}
function spawnSmoke(x,y,z,n){var c=0;for(var i=0;i<smokes.length&&c<n;i++){var s=smokes[i];if(s.life<=0){s.life=1;s.mesh.visible=true;s.mesh.position.set(x+(Math.random()-0.5),y,z+(Math.random()-0.5));s.vel.set((Math.random()-0.5)*2,2+Math.random()*2,(Math.random()-0.5)*2);s.mesh.scale.set(1,1,1);c++;}}}
function updateSmoke(dt){for(var i=0;i<smokes.length;i++){var s=smokes[i];if(s.life>0){s.life-=dt*0.8;s.mesh.position.addScaledVector(s.vel,dt);s.mesh.scale.multiplyScalar(1+dt*1.5);s.mesh.material.opacity=Math.max(0,s.life*0.5);if(s.life<=0)s.mesh.visible=false;}}}
function makeTruck(color){var g=new THREE.Group();var mat=new THREE.MeshStandardMaterial({color:color,metalness:0.6,roughness:0.35});
var lower=new THREE.Mesh(new THREE.BoxGeometry(2.3,0.65,4.6),mat);lower.position.y=1.15;lower.castShadow=true;g.add(lower);
var upper=new THREE.Mesh(new THREE.BoxGeometry(2.1,0.55,2.4),mat);upper.position.set(0,1.65,-0.3);upper.rotation.x=0.22;upper.castShadow=true;g.add(upper);
var light=new THREE.Mesh(new THREE.BoxGeometry(1.8,0.18,0.1),new THREE.MeshBasicMaterial({color:0xffffff}));light.position.set(0,1.35,2.32);g.add(light);
var tail=new THREE.Mesh(new THREE.BoxGeometry(1.9,0.15,0.1),new THREE.MeshBasicMaterial({color:0xff2222}));tail.position.set(0,1.3,-2.32);g.add(tail);
var wheelGeo=new THREE.CylinderGeometry(0.85,0.85,0.45,16);wheelGeo.rotateZ(Math.PI/2);
var hubGeo=new THREE.CylinderGeometry(0.35,0.35,0.5,10);hubGeo.rotateZ(Math.PI/2);
var wheelMat=new THREE.MeshStandardMaterial({color:0x111111,roughness:0.9});var hubMat=new THREE.MeshStandardMaterial({color:0xcccccc,metalness:0.7,roughness:0.3});
var wheels=[];[[-1.15,1.5],[1.15,1.5],[-1.15,-1.5],[1.15,-1.5]].forEach(function(p){var wg=new THREE.Group();wg.position.set(p[0],0.85,p[1]);var wm=new THREE.Mesh(wheelGeo,wheelMat);wm.castShadow=true;wg.add(wm);wg.add(new THREE.Mesh(hubGeo,hubMat));g.add(wg);wheels.push(wg);});
return {group:g,wheels:wheels};}
var WAYPOINTS=[[-20,-15],[20,-15],[20,15],[-20,15],[0,-20],[0,20]];
var spawns=[{x:0,z:20,heading:Math.PI},{x:-16,z:-16,heading:0.8},{x:16,z:-16,heading:-0.8},{x:-22,z:6,heading:1.4},{x:22,z:6,heading:-1.4}];
var truckColors=[0xd7dde3,0xe63946,0x4361ee,0xff8500,0x9d4edd];
var trucks=[];
for(var ti2=0;ti2<5;ti2++){var built=makeTruck(truckColors[ti2]);scene.add(built.group);var sp=spawns[ti2];trucks.push({group:built.group,wheels:built.wheels,pos:new THREE.Vector3(sp.x,groundH(sp.x,sp.z),sp.z),heading:sp.heading,vel:new THREE.Vector2(0,0),isPlayer:ti2===0,id:ti2,wreck:0,spinVel:0,waypoint:{x:WAYPOINTS[ti2%WAYPOINTS.length][0],z:WAYPOINTS[ti2%WAYPOINTS.length][1]},wpTimer:0,chase:false,maxSpeed:ti2===0?24:17+ti2*0.7});}
var keys={};
var audioCtx=null,engineOsc=null,engineGain=null;
function initAudio(){if(audioCtx)return;try{audioCtx=new (window.AudioContext||window.webkitAudioContext)();engineOsc=audioCtx.createOscillator();engineGain=audioCtx.createGain();engineGain.gain.value=0.03;engineOsc.type='sawtooth';engineOsc.frequency.value=60;engineOsc.connect(engineGain);engineGain.connect(audioCtx.destination);engineOsc.start();}catch(err){}}
function crashSound(v){if(!audioCtx)return;try{var o=audioCtx.createOscillator();var g=audioCtx.createGain();o.type='square';o.frequency.value=80+Math.random()*40;g.gain.value=Math.min(0.2,v);o.connect(g);g.connect(audioCtx.destination);o.start();g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+0.25);o.stop(audioCtx.currentTime+0.3);}catch(err){}}
function togglePhoto(){photoMode=!photoMode;if(photoMode){photoScoreEl.textContent='SCORE: '+score;photoEl.classList.add('show');}else{photoEl.classList.remove('show');}}
window.addEventListener('keydown',function(e){if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Space'].indexOf(e.code)>=0)e.preventDefault();keys[e.code]=true;if(e.code==='KeyP')togglePhoto();initAudio();});
window.addEventListener('keyup',function(e){keys[e.code]=false;});
window.addEventListener('resize',function(){camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);});
function angleDiff(a,b){var d=a-b;while(d>Math.PI)d-=Math.PI*2;while(d<-Math.PI)d+=Math.PI*2;return d;}
function getPlayerInput(){var throttle=0,steer=0;if(keys['KeyW']||keys['ArrowUp'])throttle=1;if(keys['KeyS']||keys['ArrowDown'])throttle=-0.6;if(keys['KeyA']||keys['ArrowLeft'])steer=1;if(keys['KeyD']||keys['ArrowRight'])steer=-1;return {throttle:throttle,steer:steer};}
function updateAI(truck,dt,t){if(truck.wreck>0)return {throttle:0,steer:0};truck.wpTimer-=dt;
if(truck.wpTimer<=0){truck.wpTimer=5+Math.random()*4;var wp=WAYPOINTS[(truck.id+Math.floor(Math.random()*WAYPOINTS.length))%WAYPOINTS.length];truck.waypoint.x=wp[0]+(truck.id-2)*3;truck.waypoint.z=wp[1]+(truck.id-1)*2;truck.chase=Math.random()<0.55;}
var dwx=truck.waypoint.x-truck.pos.x,dwz=truck.waypoint.z-truck.pos.z;if(Math.sqrt(dwx*dwx+dwz*dwz)<5)truck.wpTimer=0;
var targetX=truck.waypoint.x,targetZ=truck.waypoint.z;
if(truck.chase){var best=null,bestD=1e9;for(var i=0;i<trucks.length;i++){var o=trucks[i];if(o===truck)continue;var dx=o.pos.x-truck.pos.x,dz=o.pos.z-truck.pos.z;var d=dx*dx+dz*dz;if(d<bestD){bestD=d;best=o;}}if(best){var offA=truck.id*1.9+t*0.15;targetX=best.pos.x+Math.sin(offA)*4.5;targetZ=best.pos.z+Math.cos(offA)*4.5;}}
var sepX=0,sepZ=0;
for(var j=0;j<trucks.length;j++){var o2=trucks[j];if(o2===truck)continue;var dx2=truck.pos.x-o2.pos.x,dz2=truck.pos.z-o2.pos.z;var d2=Math.sqrt(dx2*dx2+dz2*dz2);if(d2<9&&d2>0.01){var wgt=(9-d2)/9;sepX+=dx2/d2*wgt*2.4;sepZ+=dz2/d2*wgt*2.4;}}
for(var k=0;k<obstacles.length;k++){var ob=obstacles[k];var dx3=truck.pos.x-ob.x,dz3=truck.pos.z-ob.z;var d3=Math.sqrt(dx3*dx3+dz3*dz3);if(d3<7&&d3>0.01){var w3=(7-d3)/7;sepX+=dx3/d3*w3*1.5;sepZ+=dz3/d3*w3*1.5;}}
if(Math.abs(truck.pos.x)>32)sepX+=-Math.sign(truck.pos.x)*1.5;if(Math.abs(truck.pos.z)>23)sepZ+=-Math.sign(truck.pos.z)*1.5;
var desiredX=(targetX-truck.pos.x)+sepX*6;var desiredZ=(targetZ-truck.pos.z)+sepZ*6;if(Math.abs(desiredX)<0.01&&Math.abs(desiredZ)<0.01)desiredX=1;
var desiredHeading=Math.atan2(desiredX,desiredZ);var diff=angleDiff(desiredHeading,truck.heading);var steer=Math.max(-1,Math.min(1,diff*1.8));var throttle=1;if(Math.abs(diff)>2.2)throttle=0.25;throttle*=(0.85+0.15*Math.sin(t*0.7+truck.id*2.1));return {throttle:throttle,steer:steer};}
function updateTruck(truck,input,dt){var fx=Math.sin(truck.heading),fz=Math.cos(truck.heading);
if(truck.wreck>0){truck.wreck-=dt;truck.heading+=truck.spinVel*dt;truck.spinVel*=Math.max(0,1-2*dt);truck.vel.multiplyScalar(Math.max(0,1-1.5*dt));if(Math.random()<0.3)spawnSmoke(truck.pos.x,truck.pos.y+1.5,truck.pos.z,1);}
else{var speed=truck.vel.x*fx+truck.vel.y*fz;var steerFactor=Math.max(-1,Math.min(1,speed/6));truck.heading+=input.steer*dt*1.9*steerFactor;var accel=input.throttle*16;truck.vel.x+=fx*accel*dt;truck.vel.y+=fz*accel*dt;truck.vel.multiplyScalar(Math.max(0,1-0.9*dt));var sp=truck.vel.length();if(sp>truck.maxSpeed)truck.vel.multiplyScalar(truck.maxSpeed/sp);var fSpeed=truck.vel.x*fx+truck.vel.y*fz;var latX=truck.vel.x-fx*fSpeed,latZ=truck.vel.y-fz*fSpeed;truck.vel.x-=latX*Math.min(1,6*dt);truck.vel.y-=latZ*Math.min(1,6*dt);}
truck.pos.x+=truck.vel.x*dt;truck.pos.z+=truck.vel.y*dt;
if(truck.pos.x>37){truck.pos.x=37;if(Math.abs(truck.vel.x)>5)spawnSmoke(truck.pos.x,1.5,truck.pos.z,3);truck.vel.x*=-0.4;}
if(truck.pos.x<-37){truck.pos.x=-37;if(Math.abs(truck.vel.x)>5)spawnSmoke(truck.pos.x,1.5,truck.pos.z,3);truck.vel.x*=-0.4;}
if(truck.pos.z>27){truck.pos.z=27;if(Math.abs(truck.vel.y)>5)spawnSmoke(truck.pos.x,1.5,truck.pos.z,3);truck.vel.y*=-0.4;}
if(truck.pos.z<-27){truck.pos.z=-27;if(Math.abs(truck.vel.y)>5)spawnSmoke(truck.pos.x,1.5,truck.pos.z,3);truck.vel.y*=-0.4;}
truck.pos.y=groundH(truck.pos.x,truck.pos.z);truck.group.position.copy(truck.pos);truck.group.rotation.y=truck.heading;
var hA=groundH(truck.pos.x+fx*2,truck.pos.z+fz*2),hB=groundH(truck.pos.x-fx*2,truck.pos.z-fz*2);var rx=Math.cos(truck.heading),rz=-Math.sin(truck.heading);var hR=groundH(truck.pos.x+rx*1.5,truck.pos.z+rz*1.5),hL=groundH(truck.pos.x-rx*1.5,truck.pos.z-rz*1.5);
truck.group.rotation.x=Math.atan2(hB-hA,4)*0.7;truck.group.rotation.z=Math.atan2(hL-hR,3)*0.7;
var fs=truck.vel.x*fx+truck.vel.y*fz;for(var wi=0;wi<truck.wheels.length;wi++)truck.wheels[wi].rotation.x+=fs*dt/0.85;}
function resolveTruckCollisions(){var minDist=3.4;for(var i=0;i<trucks.length;i++)for(var j=i+1;j<trucks.length;j++){var a=trucks[i],b=trucks[j];var dx=a.pos.x-b.pos.x,dz=a.pos.z-b.pos.z;var dist=Math.sqrt(dx*dx+dz*dz);if(dist<minDist&&dist>0.001){var nx=dx/dist,nz=dz/dist;var overlap=minDist-dist;a.pos.x+=nx*overlap*0.5;a.pos.z+=nz*overlap*0.5;b.pos.x-=nx*overlap*0.5;b.pos.z-=nz*overlap*0.5;var rvx=a.vel.x-b.vel.x,rvz=a.vel.y-b.vel.y;var rvAlong=rvx*nx+rvz*nz;if(rvAlong<0){var impulse=-(1+0.45)*rvAlong/2;a.vel.x+=impulse*nx;a.vel.y+=impulse*nz;b.vel.x-=impulse*nx;b.vel.y-=impulse*nz;var impact=-rvAlong;if(impact>3.5){var victim=null,other=null;if(a.isPlayer&&!b.isPlayer){victim=b;other=a;}else if(b.isPlayer&&!a.isPlayer){victim=a;other=b;}else{victim=a.vel.length()<b.vel.length()?a:b;}if(victim&&victim.wreck<=0){victim.wreck=1.4;victim.spinVel=(Math.random()*6+4)*(Math.random()<0.5?-1:1);spawnSmoke(victim.pos.x,victim.pos.y+1.5,victim.pos.z,10);crashSound(impact*0.02);if(other&&other.isPlayer){var pts=Math.round(100+impact*30);score+=pts;showMsg('RAM! +'+pts);}}else{spawnSmoke((a.pos.x+b.pos.x)/2,1.5,(a.pos.z+b.pos.z)/2,4);crashSound(impact*0.015);if(a.isPlayer||b.isPlayer)score+=Math.round(impact*10);}if(a.isPlayer||b.isPlayer)shake=Math.min(0.6,impact*0.03);}}}}}
function resolveObstacle(truck,obs){var dx=truck.pos.x-obs.x,dz=truck.pos.z-obs.z;var dist=Math.sqrt(dx*dx+dz*dz);var minDist=1.9+obs.r;if(dist<minDist&&dist>0.001){var nx=dx/dist,nz=dz/dist;var overlap=minDist-dist;truck.pos.x+=nx*overlap;truck.pos.z+=nz*overlap;var velAlong=truck.vel.x*nx+truck.vel.y*nz;if(velAlong<0){truck.vel.x-=1.3*velAlong*nx;truck.vel.y-=1.3*velAlong*nz;truck.vel.multiplyScalar(0.85);if(Math.abs(velAlong)>4){spawnSmoke(truck.pos.x,1.5,truck.pos.z,4);crashSound(Math.abs(velAlong)*0.015);if(truck.isPlayer)score+=Math.round(Math.abs(velAlong)*5);if(obs.type==='barrel'&&!obs.tipped){obs.tipped=true;obs.mesh.rotation.z=Math.PI/2;obs.mesh.position.y=0.65+groundH(obs.x,obs.z);}if(obs.type==='crate'){obs.mesh.rotation.y+=0.7;obs.x+=nx*0.8;obs.z+=nz*0.8;obs.mesh.position.x=obs.x;obs.mesh.position.z=obs.z;}if(obs.type==='tire'){obs.mesh.rotation.y+=0.5;}}}}}
var camPos=new THREE.Vector3(0,10,34);
function updateCamera(dt){var p=trucks[0];var fx=Math.sin(p.heading),fz=Math.cos(p.heading);var dx=p.pos.x-fx*11,dz=p.pos.z-fz*11,dy=p.pos.y+5.5;camPos.x+=(dx-camPos.x)*Math.min(1,4*dt);camPos.y+=(dy-camPos.y)*Math.min(1,4*dt);camPos.z+=(dz-camPos.z)*Math.min(1,4*dt);camera.position.set(camPos.x+(Math.random()-0.5)*shake,camPos.y+(Math.random()-0.5)*shake,camPos.z+(Math.random()-0.5)*shake);shake*=Math.max(0,1-3*dt);camera.lookAt(p.pos.x+fx*6,p.pos.y+1.5,p.pos.z+fz*6);}
var last=performance.now();
function animate(now){requestAnimationFrame(animate);if(photoMode)return;var dt=Math.min(0.033,(now-last)/1000);last=now;if(dt<=0)dt=0.016;var t=now/1000;
if(timeLeft>0){timeLeft-=dt;if(timeLeft<0)timeLeft=0;}
var playerInput=getPlayerInput();
for(var i=0;i<trucks.length;i++){var tr=trucks[i];var inp=tr.isPlayer?playerInput:updateAI(tr,dt,t);updateTruck(tr,inp,dt);}
resolveTruckCollisions();
for(var a=0;a<trucks.length;a++)for(var b=0;b<obstacles.length;b++)resolveObstacle(trucks[a],obstacles[b]);
updateCrowd(t);updateSmoke(dt);updateCamera(dt);
scoreEl.textContent='SCORE: '+score;clockEl.textContent='TIME: '+Math.ceil(timeLeft);
if(msgTimer>0){msgTimer-=dt;if(msgTimer<=0)msgEl.style.opacity='0';}
if(audioCtx&&engineOsc){engineOsc.frequency.value=50+trucks[0].vel.length()*5;}
renderer.render(scene,camera);}
requestAnimationFrame(animate);
})();
</script>
</body>
</html>
if (window.location.hash === '#demo') { timeLeft = 20; score = 5000; } sets the real timeLeft and score variables that the countdown and HUD rendering read, so the clock ticks down from 20, collisions add to 5000, and photo mode reports the live score, rather than painting numbers over a normal 90 second zero score game.animate returns early when photoMode is true, so truck updates, collisions, clock, crowd, smoke, camera and renderer.render do not run and the last frame stays frozen behind the overlay until P is pressed again.