27:18Gemini 4 Hydrogen: Cheaper Tokens, More Expensive Answers?
Nov 13, 2026 · YouTube
18:40ERNIE 5.5 Flash Preview vs GLM 5.3 Flash: Baidu's new model is very impressive!
Nov 11, 2026 · YouTube
36:22Fable 5.2 robbing me blind, plus: Nex AGI's N2.6 vs GLM 5.3 Flash
Nov 11, 2026 · YouTube
14:03Gemini 4 Carbon: Can it handle my challenges? (First look with one-shot coding tests)
Nov 10, 2026 · YouTube
47:51Ranking major U.S. Open-Weights Models. (Where does Ripple 65B fit in?)
Nov 6, 2026 · YouTube
10:57"Crouton Alpha": the latest super-efficient coding model by Meta?
Nov 3, 2026 · YouTube
38:58Testing Particle-1: a post-trained DeepSeek V4.1 Flash specializing in coding and efficiency
Oct 28, 2026 · YouTube
22:14GPT-6.1 Terra Pro vs Terra: How Much Better Is Pro?
Oct 27, 2026 · YouTube
29:55Kimi K3.1 Pro Looked Incredible. Then I Kept Testing.
Oct 26, 2026 · YouTube
31:28Is this company reselling GPT Sol 6.1 at a loss (for non-agentic/text-only)?🤔 Is this sustainable?
Oct 20, 2026 · YouTube
17:58Nemotron 3.5 Super VL vs Opus 5.5: Testing NVIDIA’s new coding juggernaut
Oct 15, 2026 · YouTube
16:04Testing a *massive* new open-weights model 🤯: Muse Spark Open (vs. Nemotron, Opus 5.5, and more)
Oct 15, 2026 · YouTube
16:43Gemini 4 Argon: Great 🐈 Animation, but a broken escalator. (Coding challenge vs Opus 5.5, Sol 6.1)
Oct 13, 2026 · YouTube
16:37Meta's new "Muse Wonder" AI is impressive... but can it compete with Anthropic's models?
Oct 12, 2026 · YouTube
11:12WOW... Haiku 5.5 is very impressive. RIP OpenAI?
Oct 7, 2026 · YouTube
22:47Mistral Large 4 Is Cheap. But Is It Good Enough to Compete?
Oct 6, 2026 · YouTube
22:02Is Pareto 26.10 Preview Just Routing to GPT Sol? I Tested It Against Sol 6.1 Pro
Oct 1, 2026 · YouTube
11:22I Tested Sonnet 5.5 Against Opus 5.5. Is It Actually Cheaper?
Sep 28, 2026 · YouTube
30:00Did Elon Musk's team "train the gay away"... and make Grok 4.7 worse?
Sep 26, 2026 · YouTube
10:58So... how much better is Claude Opus 5.5 vs. GPT-6 Sol, really?
Sep 23, 2026 · YouTube
9:38Claude Opus 5.5... the BEST new model for coding!! 🤩
Sep 22, 2026 · YouTube
29:00Grok 4.7: improved, but GLM 5.3 FlashX beat it anyway (benchmark against Gemini, Sol, etc.)
Sep 22, 2026 · YouTube
16:54Stealth Union Alpha: Is this another cheap GPT model? (better than Luna, cheaper than Terra?)
Sep 16, 2026 · YouTube
16:45DeepSeek V4.1 Flash Is Shockingly Good for the Price
Sep 10, 2026 · YouTube
17:14How much better is GPT-6 Astra over Claude Opus 5? Real Website and Coding Tests
Sep 5, 2026 · YouTube
29:13Implementing a multiplayer game with GPT-6 Astra (+ Gemini 3.8, Opus 5, Sol Pro)
Sep 5, 2026 · YouTube
19:42Meta Muse Spark 1.3 Review: A Huge Leap Forward
Sep 2, 2026 · YouTube
17:31Testing Gemini 3.8 Flash on Real Coding Challenges (vs. other models)
Sep 2, 2026 · YouTube
19:45Fable 5.1 writes an animated novel using Ren'Py... did it do better than Sol Pro 5.6?
Sep 1, 2026 · YouTube
14:09Testing IBM Granite-4.2-8B! Can it name 100 women?
Aug 31, 2026 · YouTube
17:37Tencent Hy4 Preview Tested: Is It Really Better Than Kimi K3 and GLM 5.3?
Aug 28, 2026 · YouTube
23:58Qwen 3.8 Flash Tested: UI, Coding, Physics & more - compared to Opus 5, Sol Pro, Grok
Aug 27, 2026 · YouTube
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.Kilovolt Coliseum — a night-lit dirt bowl at the Fremont Fairgrounds where five stainless Cybertrucks on monster tires launch off packed-dirt hills, crush junkers and T-bone each other under floodlights while ~3,400 fans do the wave.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Kilovolt Coliseum — Cybertruck Monster Rally</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#05070d;font-family:system-ui,"Segoe UI",Helvetica,Arial,sans-serif;color:#eef;user-select:none}
canvas{display:block}
#hud{position:fixed;inset:0;pointer-events:none}
.box{position:absolute;padding:8px 14px;background:rgba(5,8,16,.62);border:1px solid rgba(120,220,255,.35);border-radius:8px;backdrop-filter:blur(4px)}
#arena{left:16px;top:16px;font-weight:800;letter-spacing:2px;font-size:18px;color:#7ee0ff}
#arena small{display:block;font-weight:400;letter-spacing:1px;font-size:11px;color:#cde;margin-top:2px}
#clock{left:50%;top:16px;transform:translateX(-50%);font-size:30px;font-weight:800;font-variant-numeric:tabular-nums;min-width:90px;text-align:center}
#score{right:16px;top:16px;font-size:26px;font-weight:800;text-align:right;min-width:120px;font-variant-numeric:tabular-nums}
#score small{display:block;font-size:10px;letter-spacing:2px;font-weight:600;color:#9bd}
#hpbar{height:5px;background:#2a3140;margin-top:7px;border-radius:3px;overflow:hidden}
#hpbar i{display:block;height:100%;width:100%;background:#4cf;border-radius:3px;transition:width .15s}
#controls{left:50%;bottom:14px;transform:translateX(-50%);font-size:12px;color:#bcd;white-space:nowrap}
#controls b{color:#fff}
#msg{position:absolute;left:50%;top:36%;transform:translate(-50%,-50%);font-size:44px;font-weight:900;color:#ffd54a;text-shadow:0 0 18px #f80,0 2px 2px #000;opacity:0;transition:opacity .25s;white-space:nowrap;letter-spacing:1px}
.screen{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(3,5,10,.72)}
.hidden{display:none!important}
.card{text-align:center;padding:34px 46px;background:rgba(8,12,22,.9);border:1px solid rgba(120,220,255,.5);border-radius:14px;box-shadow:0 0 60px rgba(60,180,255,.25);max-width:560px}
.card h1{margin:0 0 4px;letter-spacing:4px;font-size:34px;color:#7ee0ff}
.card p{margin:8px 0;color:#cde;line-height:1.5}
.card .tag{font-size:12px;letter-spacing:4px;color:#ffd54a;margin-bottom:8px}
.card .big{font-size:64px;font-weight:900;margin:10px 0 0;font-variant-numeric:tabular-nums}
.card .small{font-size:12px;color:#9ab}
button{pointer-events:auto;font:inherit;font-weight:800;letter-spacing:2px;font-size:16px;padding:12px 28px;margin:14px 0 6px;border-radius:8px;border:1px solid #7ee0ff;background:#0d3a52;color:#fff;cursor:pointer}
button:hover{background:#155f85}
</style>
</head>
<body>
<div id="hud">
<div class="box" id="arena">KILOVOLT COLISEUM<small id="arenaSub">Fremont Fairgrounds · Monster Rally</small></div>
<div class="box" id="clock">2:00</div>
<div class="box" id="score"><span id="scoreVal">0</span><small>SCORE</small><div id="hpbar"><i id="hpFill"></i></div></div>
<div class="box" id="controls"><b>W A S D</b> or <b>← ↑ ↓ →</b> drive · <b>SPACE</b> handbrake · <b>P</b> photo mode · <b>R</b> restart after the buzzer</div>
<div id="msg"></div>
</div>
<div id="start" class="screen">
<div class="card">
<div class="tag">TESLA CYBERTRUCK MONSTER RALLY</div>
<h1>KILOVOLT COLISEUM</h1>
<p>Five trucks. One dirt bowl. Two minutes.</p>
<p>Drive with <b>W A S D</b> or the <b>Arrow keys</b>, <b>SPACE</b> to slide. Ram rivals for points, crush the junkers, launch off the hills, wreck the four AI trucks before the buzzer.</p>
<button id="startBtn">START ENGINES</button>
<p class="small">Press <b>P</b> any time for photo mode.</p>
</div>
</div>
<div id="photo" class="screen hidden">
<div class="card">
<div class="tag">PHOTO MODE</div>
<h1>KILOVOLT COLISEUM</h1>
<div class="big" id="photoScore">0</div>
<p>SCORE</p>
<p class="small">Simulation frozen · press <b>P</b> to resume</p>
</div>
</div>
<div id="over" class="screen hidden">
<div class="card">
<div class="tag">FINAL BUZZER</div>
<h1>KILOVOLT COLISEUM</h1>
<div class="big" id="overScore">0</div>
<p>FINAL SCORE</p>
<button id="restartBtn">RUN IT AGAIN (R)</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js"></script>
<script>
(function(){
'use strict';
var $=function(id){return document.getElementById(id);};
if(!window.THREE){$('startBtn').textContent='three.js failed to load — check network';return;}
/* ---------- constants & state ---------- */
var ARENA='KILOVOLT COLISEUM';
var W=46,D=30,GRAV=24;
var DEMO=location.hash==='#demo';
var ROUND_TIME=DEMO?20:120, START_SCORE=DEMO?5000:0;
var state={time:ROUND_TIME,score:START_SCORE,started:false,over:false,photo:false,excite:0,shake:0,msgT:0};
if(DEMO)$('arenaSub').textContent='Fremont Fairgrounds · Monster Rally · DEMO';
var rand=function(a,b){return a+Math.random()*(b-a);};
function clamp(v,a,b){return v<a?a:v>b?b:v;}
function wrap(a){while(a>Math.PI)a-=2*Math.PI;while(a<-Math.PI)a+=2*Math.PI;return a;}
/* ---------- terrain ---------- */
var hills=[{x:-22,z:-10,r:9,h:3.2},{x:24,z:10,r:10,h:3.6},{x:0,z:0,r:7,h:2.4},{x:-18,z:14,r:7,h:2.6},{x:20,z:-14,r:7,h:2.8},{x:-38,z:0,r:6,h:2}];
function groundH(x,z){var y=0;for(var i=0;i<hills.length;i++){var b=hills[i],dx=x-b.x,dz=z-b.z,d2=dx*dx+dz*dz,r2=b.r*b.r;if(d2<r2){var t=1-d2/r2;y+=b.h*t*t;}}return y;}
function groundN(x,z,out){var e=0.4,gx=(groundH(x+e,z)-groundH(x-e,z))/(2*e),gz=(groundH(x,z+e)-groundH(x,z-e))/(2*e);return out.set(-gx,1,-gz).normalize();}
/* ---------- scene ---------- */
var scene=new THREE.Scene();scene.background=new THREE.Color(0x070a14);scene.fog=new THREE.Fog(0x070a14,90,260);
var camera=new THREE.PerspectiveCamera(60,innerWidth/innerHeight,0.1,400);
var renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio||1,1.5));renderer.setSize(innerWidth,innerHeight);
renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap;renderer.outputEncoding=THREE.sRGBEncoding;
document.body.insertBefore(renderer.domElement,document.body.firstChild);
scene.add(new THREE.HemisphereLight(0x5a6a9a,0x1a1208,0.8));
var sun=new THREE.DirectionalLight(0xfff2dc,1.5);sun.position.set(30,70,20);sun.castShadow=true;
sun.shadow.mapSize.set(2048,2048);var sc=sun.shadow.camera;sc.left=-75;sc.right=75;sc.top=55;sc.bottom=-55;sc.near=10;sc.far=180;sun.shadow.bias=-0.0006;
scene.add(sun);scene.add(sun.target);
/* floor */
var floorGeo=new THREE.PlaneGeometry(2*W,2*D,92,60);floorGeo.rotateX(-Math.PI/2);
(function(){var p=floorGeo.attributes.position,cols=[];for(var i=0;i<p.count;i++){var x=p.getX(i),z=p.getZ(i),y=groundH(x,z);p.setY(i,y);var s=0.78+y*0.1+Math.sin(x*1.3)*Math.cos(z*1.7)*0.05;cols.push(0.46*s,0.36*s,0.24*s);}floorGeo.setAttribute('color',new THREE.Float32BufferAttribute(cols,3));floorGeo.computeVertexNormals();})();
var floor=new THREE.Mesh(floorGeo,new THREE.MeshStandardMaterial({vertexColors:true,roughness:1,metalness:0}));floor.receiveShadow=true;scene.add(floor);
var outer=new THREE.Mesh(new THREE.PlaneGeometry(600,600),new THREE.MeshStandardMaterial({color:0x0c0f18,roughness:1}));outer.rotation.x=-Math.PI/2;outer.position.y=-0.05;scene.add(outer);
/* walls */
var wallMat=new THREE.MeshStandardMaterial({color:0x9a9a96,roughness:0.9});
function wall(w,d,x,z){var m=new THREE.Mesh(new THREE.BoxGeometry(w,1.5,d),wallMat);m.position.set(x,0.75,z);m.castShadow=m.receiveShadow=true;scene.add(m);}
wall(2*W+2,1,0,-D-0.5);wall(2*W+2,1,0,D+0.5);wall(1,2*D,-W-0.5,0);wall(1,2*D,W+0.5,0);
/* stands + crowd */
var seats=[];
(function(){
var tm1=new THREE.MeshStandardMaterial({color:0x3a4250,roughness:0.9}),tm2=new THREE.MeshStandardMaterial({color:0x2c3340,roughness:0.9});
for(var i=0;i<9;i++){
var inx=W+2+i*1.7,inz=D+2+i*1.7,hgt=1.4+i*1.1,mat=i%2?tm1:tm2;
var a=new THREE.Mesh(new THREE.BoxGeometry(2*inx+3.4,hgt,1.7),mat);a.position.set(0,hgt/2,inz+0.85);a.receiveShadow=true;scene.add(a);
var b=a.clone();b.position.z=-inz-0.85;scene.add(b);
var c=new THREE.Mesh(new THREE.BoxGeometry(1.7,hgt,2*inz),mat);c.position.set(inx+0.85,hgt/2,0);c.receiveShadow=true;scene.add(c);
var d=c.clone();d.position.x=-inx-0.85;scene.add(d);
for(var x=-inx-1.2;x<=inx+1.2;x+=0.9){
if(Math.random()>0.1)seats.push({x:x+rand(-.15,.15),y:hgt,z:inz+0.85+rand(-.2,.2)});
if(Math.random()>0.1)seats.push({x:x+rand(-.15,.15),y:hgt,z:-inz-0.85+rand(-.2,.2)});
}
for(var z=-inz+0.6;z<=inz-0.6;z+=0.9){
if(Math.random()>0.1)seats.push({x:inx+0.85+rand(-.2,.2),y:hgt,z:z+rand(-.15,.15)});
if(Math.random()>0.1)seats.push({x:-inx-0.85+rand(-.2,.2),y:hgt,z:z+rand(-.15,.15)});
}
}
var bx=W+2+9*1.7,bz=D+2+9*1.7,bm=new THREE.MeshStandardMaterial({color:0x1b2029,roughness:0.95});
var bw=new THREE.Mesh(new THREE.BoxGeometry(2*bx+6,16,1.5),bm);bw.position.set(0,8,bz+0.75);scene.add(bw);var bw2=bw.clone();bw2.position.z=-bz-0.75;scene.add(bw2);
var bw3=new THREE.Mesh(new THREE.BoxGeometry(1.5,16,2*bz),bm);bw3.position.set(bx+0.75,8,0);scene.add(bw3);var bw4=bw3.clone();bw4.position.x=-bx-0.75;scene.add(bw4);
var lamp=new THREE.MeshBasicMaterial({color:0xfff4d0}),lg=new THREE.BoxGeometry(2.4,0.6,0.5);
for(var lx=-bx+6;lx<bx;lx+=12){var l1=new THREE.Mesh(lg,lamp);l1.position.set(lx,15.5,bz);scene.add(l1);var l2=l1.clone();l2.position.z=-bz;scene.add(l2);}
var lg2=new THREE.BoxGeometry(0.5,0.6,2.4);
for(var lz=-bz+6;lz<bz;lz+=12){var l3=new THREE.Mesh(lg2,lamp);l3.position.set(bx,15.5,lz);scene.add(l3);var l4=l3.clone();l4.position.x=-bx;scene.add(l4);}
var tower=new THREE.CylinderGeometry(0.5,0.7,34,8),tmat=new THREE.MeshStandardMaterial({color:0x555b66,roughness:0.7});
[[1,1],[-1,1],[1,-1],[-1,-1]].forEach(function(s){var t=new THREE.Mesh(tower,tmat);t.position.set(s[0]*(bx+4),17,s[1]*(bz+4));scene.add(t);var head=new THREE.Mesh(new THREE.BoxGeometry(5,1.2,5),lamp);head.position.set(s[0]*(bx+3),34,s[1]*(bz+3));scene.add(head);});
})();
var N=seats.length;
var bodyIM=new THREE.InstancedMesh(new THREE.BoxGeometry(0.55,0.85,0.4),new THREE.MeshStandardMaterial({roughness:0.9}),N);
var headIM=new THREE.InstancedMesh(new THREE.SphereGeometry(0.2,6,5),new THREE.MeshStandardMaterial({roughness:0.8}),N);
(function(){var dm=new THREE.Object3D(),col=new THREE.Color(),skins=[0xf1c9a5,0xd9a276,0xa86b3f,0x6b4126,0xf6d8bf];
for(var i=0;i<N;i++){var s=seats[i];s.phase=Math.atan2(s.z,s.x)*3+rand(0,1);
dm.position.set(s.x,s.y+0.42,s.z);dm.rotation.y=Math.atan2(s.x,s.z);dm.updateMatrix();bodyIM.setMatrixAt(i,dm.matrix);bodyIM.setColorAt(i,col.setHSL(Math.random(),0.75,rand(.35,.6)));
dm.position.y=s.y+1.05;dm.updateMatrix();headIM.setMatrixAt(i,dm.matrix);headIM.setColorAt(i,col.setHex(skins[Math.floor(Math.random()*skins.length)]));}
bodyIM.instanceMatrix.setUsage(THREE.DynamicDrawUsage);headIM.instanceMatrix.setUsage(THREE.DynamicDrawUsage);scene.add(bodyIM);scene.add(headIM);})();
function updateCrowd(t){var ba=bodyIM.instanceMatrix.array,ha=headIM.instanceMatrix.array,amp=0.06+state.excite*0.4,spd=5+state.excite*3;
for(var i=0;i<N;i++){var s=seats[i],bob=amp*Math.max(0,Math.sin(t*spd+s.phase));ba[i*16+13]=s.y+0.42+bob;ha[i*16+13]=s.y+1.05+bob;}
bodyIM.instanceMatrix.needsUpdate=true;headIM.instanceMatrix.needsUpdate=true;}
/* ---------- particles ---------- */
function Particles(n,color,size,grav,rate,opacity){this.n=n;this.grav=grav;this.rate=rate;this.i=0;
this.pos=new Float32Array(n*3);this.vel=new Float32Array(n*3);this.life=new Float32Array(n);
for(var i=0;i<n;i++)this.pos[i*3+1]=-100;
var g=new THREE.BufferGeometry();this.attr=new THREE.BufferAttribute(this.pos,3);this.attr.setUsage(THREE.DynamicDrawUsage);g.setAttribute('position',this.attr);
this.points=new THREE.Points(g,new THREE.PointsMaterial({color:color,size:size,transparent:true,opacity:opacity,depthWrite:false}));this.points.frustumCulled=false;scene.add(this.points);}
Particles.prototype.emit=function(x,y,z,count,spread,up){for(var k=0;k<count;k++){var i=this.i=(this.i+1)%this.n;this.pos[i*3]=x;this.pos[i*3+1]=y;this.pos[i*3+2]=z;
this.vel[i*3]=rand(-spread,spread);this.vel[i*3+1]=rand(0,up)+spread*0.3;this.vel[i*3+2]=rand(-spread,spread);this.life[i]=1;}};
Particles.prototype.update=function(dt){for(var i=0;i<this.n;i++){if(this.life[i]<=0)continue;this.life[i]-=dt*this.rate;if(this.life[i]<=0){this.pos[i*3+1]=-100;continue;}
this.pos[i*3]+=this.vel[i*3]*dt;this.pos[i*3+1]+=this.vel[i*3+1]*dt;this.pos[i*3+2]+=this.vel[i*3+2]*dt;this.vel[i*3+1]-=this.grav*dt;}this.attr.needsUpdate=true;};
var sparks=new Particles(500,0xffb347,0.35,20,2.2,0.95),dust=new Particles(400,0x8a7355,1.1,3,1.2,0.5),smoke=new Particles(400,0x444444,1.6,-2,0.7,0.5);
/* ---------- truck mesh ---------- */
function shapeFrom(pts){var s=new THREE.Shape();s.moveTo(pts[0][0],pts[0][1]);for(var i=1;i<pts.length;i++)s.lineTo(pts[i][0],pts[i][1]);return s;}
function makeTruckMesh(accent){
var g=new THREE.Group();
var bodyMat=new THREE.MeshStandardMaterial({color:0xc4c9ce,metalness:0.9,roughness:0.3,flatShading:true});
var bodyGeo=new THREE.ExtrudeGeometry(shapeFrom([[-2.7,0.5],[2.8,0.5],[2.9,0.95],[2.6,1.1],[1.2,1.3],[0.4,2.05],[-0.7,2.1],[-2.75,1.5]]),{depth:2.3,bevelEnabled:false});bodyGeo.translate(0,0,-1.15);
var body=new THREE.Mesh(bodyGeo,bodyMat);body.castShadow=true;g.add(body);
var glassMat=new THREE.MeshStandardMaterial({color:0x0b1620,metalness:0.6,roughness:0.15});
var glassGeo=new THREE.ExtrudeGeometry(shapeFrom([[1.25,1.32],[0.42,2.02],[-0.66,2.07],[-1.75,1.55],[-1.6,1.42]]),{depth:2.34,bevelEnabled:false});glassGeo.translate(0,0,-1.17);g.add(new THREE.Mesh(glassGeo,glassMat));
var ws=new THREE.Mesh(new THREE.PlaneGeometry(2.2,1.05),glassMat);var nx=0.75,ny=0.8,nl=Math.sqrt(nx*nx+ny*ny);nx/=nl;ny/=nl;ws.position.set(0.8+nx*0.02,1.675+ny*0.02,0);ws.lookAt(ws.position.x+nx,ws.position.y+ny,0);g.add(ws);
var acc=new THREE.MeshStandardMaterial({color:accent,emissive:accent,emissiveIntensity:0.9,roughness:0.5});
var head=new THREE.Mesh(new THREE.BoxGeometry(0.06,0.1,2.2),new THREE.MeshBasicMaterial({color:0xffffff}));head.position.set(2.87,1.0,0);g.add(head);
var tail=new THREE.Mesh(new THREE.BoxGeometry(0.06,0.1,2.2),new THREE.MeshBasicMaterial({color:0xff2020}));tail.position.set(-2.73,1.45,0);g.add(tail);
var bar=new THREE.Mesh(new THREE.BoxGeometry(1.0,0.14,1.9),acc);bar.position.set(-0.15,2.18,0);g.add(bar);
var st1=new THREE.Mesh(new THREE.BoxGeometry(4.6,0.16,0.04),acc);st1.position.set(0,0.85,1.17);g.add(st1);var st2=st1.clone();st2.position.z=-1.17;g.add(st2);
var tireGeo=new THREE.CylinderGeometry(1.05,1.05,0.85,16);tireGeo.rotateX(Math.PI/2);
var hubGeo=new THREE.CylinderGeometry(0.55,0.55,0.87,10);hubGeo.rotateX(Math.PI/2);
var tireMat=new THREE.MeshStandardMaterial({color:0x151515,roughness:0.95}),hubMat=new THREE.MeshStandardMaterial({color:0x9aa0a6,metalness:0.8,roughness:0.4});
var wheels=[];[[1.75,1.35],[1.75,-1.35],[-1.75,1.35],[-1.75,-1.35]].forEach(function(w){var piv=new THREE.Group();piv.position.set(w[0],0,w[1]);var spin=new THREE.Group();var tire=new THREE.Mesh(tireGeo,tireMat);tire.castShadow=true;spin.add(tire);spin.add(new THREE.Mesh(hubGeo,hubMat));piv.add(spin);piv.userData.front=w[0]>0;piv.userData.spin=spin;g.add(piv);wheels.push(piv);});
var pole=new THREE.Mesh(new THREE.CylinderGeometry(0.03,0.03,1.6,5),hubMat);pole.position.set(-2.3,2.6,-0.9);g.add(pole);
var flag=new THREE.Mesh(new THREE.PlaneGeometry(0.9,0.5),new THREE.MeshBasicMaterial({color:accent,side:THREE.DoubleSide}));flag.position.set(-1.85,3.2,-0.9);g.add(flag);
g.userData={bodyMat:bodyMat,wheels:wheels,flag:flag,pole:pole};return g;
}
/* ---------- trucks ---------- */
var DEFS=[
{name:'YOU',color:0x39d0ff,player:true,spawn:[0,22]},
{name:'REDLINE',color:0xff3b3b,aggr:0.9,max:20,spawn:[-38,-20]},
{name:'GOLDRUSH',color:0xffc63a,aggr:0.55,max:19,spawn:[38,-20]},
{name:'VENOM',color:0x5cff5c,aggr:0.4,max:21,spawn:[-38,20]},
{name:'VIOLET',color:0xc45cff,aggr:0.75,max:18,spawn:[38,20]}];
var trucks=DEFS.map(function(def,i){var m=makeTruckMesh(def.color);scene.add(m);
return {def:def,player:!!def.player,mesh:m,x:0,z:0,y:0,vx:0,vz:0,vy:0,h:0,air:false,airT:0,steer:0,throttle:0,brake:false,hp:100,wrecked:false,wreckT:0,r:2.4,flash:0,lastHit:null,lastHitT:0,
accel:def.player?14:12.5,maxSpeed:def.player?22:def.max,ai:{aggr:def.aggr||0.6,retarget:rand(0,2),target:null,stuck:0,reverseT:0,side:i%2?1:-1}};});
var player=trucks[0],rivals=trucks.slice(1);
var SPAWNS=[[-38,-20],[38,-20],[-38,20],[38,20],[0,-22],[0,22]];
function placeTruck(t,x,z){t.x=x;t.z=z;t.h=Math.atan2(z,-x);t.vx=t.vz=t.vy=0;t.air=false;t.y=groundH(x,z);t.hp=100;t.wrecked=false;t.wreckT=0;t.flash=0;t.lastHit=null;
t.mesh.userData.bodyMat.color.setHex(0xc4c9ce);t.mesh.userData.bodyMat.emissive.setHex(0);t.mesh.userData.flag.visible=true;t.mesh.userData.pole.visible=true;
t.mesh.position.set(x,t.y+1.05,z);t.mesh.quaternion.setFromAxisAngle(new THREE.Vector3(0,1,0),t.h);}
function respawn(t){var best=null,bd=-1;SPAWNS.forEach(function(s){var md=1e9;trucks.forEach(function(o){if(o===t)return;var dx=o.x-s[0],dz=o.z-s[1];md=Math.min(md,dx*dx+dz*dz);});if(md>bd){bd=md;best=s;}});placeTruck(t,best[0],best[1]);}
/* ---------- obstacles ---------- */
var obstacles=[];
function makeTires(){var g=new THREE.Group(),m=new THREE.MeshStandardMaterial({color:0x1a1a1a,roughness:0.95}),geo=new THREE.TorusGeometry(0.68,0.3,8,16);geo.rotateX(Math.PI/2);for(var k=0;k<3;k++){var t=new THREE.Mesh(geo,m);t.position.y=0.3+k*0.55;t.castShadow=true;g.add(t);}return g;}
function makeBarrier(){var g=new THREE.Group(),cm=new THREE.MeshStandardMaterial({color:0xb5b5b0,roughness:0.9}),rm=new THREE.MeshStandardMaterial({color:0xd82626,roughness:0.8});
var base=new THREE.Mesh(new THREE.BoxGeometry(3.2,0.5,0.9),cm);base.position.y=0.25;base.castShadow=true;g.add(base);var top=new THREE.Mesh(new THREE.BoxGeometry(3.2,0.6,0.5),cm);top.position.y=0.8;top.castShadow=true;g.add(top);
[-1.1,0,1.1].forEach(function(x){var s=new THREE.Mesh(new THREE.BoxGeometry(0.5,0.62,0.52),rm);s.position.set(x,0.8,0);g.add(s);});return g;}
function makeJunk(color){var g=new THREE.Group(),bm=new THREE.MeshStandardMaterial({color:color,roughness:0.85,metalness:0.2}),dm=new THREE.MeshStandardMaterial({color:0x3a2a1a,roughness:0.9});
var b=new THREE.Mesh(new THREE.BoxGeometry(3.6,0.9,1.8),bm);b.position.y=0.75;b.castShadow=true;g.add(b);var c=new THREE.Mesh(new THREE.BoxGeometry(1.6,0.7,1.6),dm);c.position.set(-0.2,1.55,0);c.castShadow=true;g.add(c);
var wg=new THREE.CylinderGeometry(0.4,0.4,0.3,10);wg.rotateX(Math.PI/2);var wm=new THREE.MeshStandardMaterial({color:0x111111});[[1.2,0.95],[1.2,-0.95],[-1.2,0.95],[-1.2,-0.95]].forEach(function(p){var w=new THREE.Mesh(wg,wm);w.position.set(p[0],0.4,p[1]);g.add(w);});return g;}
var OBS_DEFS=[
['tire',-8,-22],['tire',12,20],['tire',-30,-22],['tire',32,22],['tire',8,-8],['tire',-8,8],['tire',34,-2],['tire',-30,14],
['barrier',22,26,0],['barrier',-22,-26,0],['barrier',-42,10,Math.PI/2],['barrier',42,-10,Math.PI/2],['barrier',-12,-2,0.7],['barrier',12,2,-0.7],
['junk',-26,2,0.4,0x7a3b1e],['junk',26,-4,-0.3,0x3b5a7a],['junk',2,-16,1.2,0x6a6a2a],['junk',-2,16,-1.0,0x5a2a5a]];
OBS_DEFS.forEach(function(d){var o={type:d[0],x0:d[1],z0:d[2],x:d[1],z:d[2],rot:d[3]||0,vx:0,vz:0,vy:0,yoff:0,crushed:false};
if(o.type==='tire'){o.mesh=makeTires();o.r=0.95;}else if(o.type==='barrier'){o.mesh=makeBarrier();o.r=1.7;}else{o.mesh=makeJunk(d[4]);o.r=2.0;}
o.mesh.rotation.y=o.rot;scene.add(o.mesh);obstacles.push(o);});
function resetObstacles(){obstacles.forEach(function(o){o.x=o.x0;o.z=o.z0;o.vx=o.vz=o.vy=0;o.yoff=0;o.crushed=false;o.mesh.scale.y=1;o.mesh.rotation.y=o.rot;o.mesh.position.set(o.x,groundH(o.x,o.z),o.z);});}
/* ---------- audio (Web Audio, generated) ---------- */
var AC=null,engOsc,engGain,roarGain,noiseBuf,master;
function initAudio(){if(AC)return;try{var Ctx=window.AudioContext||window.webkitAudioContext;if(!Ctx)return;AC=new Ctx();master=AC.createGain();master.gain.value=0.6;master.connect(AC.destination);
engOsc=AC.createOscillator();engOsc.type='sawtooth';var f=AC.createBiquadFilter();f.type='lowpass';f.frequency.value=450;engGain=AC.createGain();engGain.gain.value=0;engOsc.connect(f);f.connect(engGain);engGain.connect(master);engOsc.start();
noiseBuf=AC.createBuffer(1,AC.sampleRate,AC.sampleRate);var d=noiseBuf.getChannelData(0);for(var i=0;i<d.length;i++)d[i]=Math.random()*2-1;
var src=AC.createBufferSource();src.buffer=noiseBuf;src.loop=true;var bp=AC.createBiquadFilter();bp.type='bandpass';bp.frequency.value=500;bp.Q.value=0.6;roarGain=AC.createGain();roarGain.gain.value=0.02;src.connect(bp);bp.connect(roarGain);roarGain.connect(master);src.start();
if(AC.state==='suspended')AC.resume();}catch(e){AC=null;}}
function sndCrash(p){if(!AC)return;try{var s=AC.createBufferSource();s.buffer=noiseBuf;var f=AC.createBiquadFilter();f.type='lowpass';f.frequency.value=900;var g=AC.createGain();var n=AC.currentTime;g.gain.setValueAtTime(clamp(p/14,0.05,1),n);g.gain.exponentialRampToValueAtTime(0.001,n+0.4);s.connect(f);f.connect(g);g.connect(master);s.start(n);s.stop(n+0.45);}catch(e){}}
function updateAudio(active){if(!AC)return;var fx=Math.cos(player.h),fz=-Math.sin(player.h),vf=player.vx*fx+player.vz*fz;
engGain.gain.value=active?0.06:0;engOsc.frequency.value=55+Math.abs(vf)*5+Math.max(0,player.throttle)*25;roarGain.gain.value=active?0.02+state.excite*0.08:0;}
/* ---------- input ---------- */
var keys={};
addEventListener('keydown',function(e){var k=e.key;if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].indexOf(k)>=0)e.preventDefault();keys[k.length===1?k.toLowerCase():k]=true;
if(k==='p'||k==='P'){if(state.started&&!state.over)togglePhoto();}
if((k==='r'||k==='R')&&state.over)restart();
if(!state.started&&(k==='Enter'||k===' '))startGame();});
addEventListener('keyup',function(e){var k=e.key;keys[k.length===1?k.toLowerCase():k]=false;});
$('startBtn').addEventListener('click',startGame);$('restartBtn').addEventListener('click',restart);
function readInput(dt){var th=(keys.w||keys.ArrowUp?1:0)-(keys.s||keys.ArrowDown?1:0),st=(keys.a||keys.ArrowLeft?1:0)-(keys.d||keys.ArrowRight?1:0);
player.throttle=th;player.brake=!!keys[' '];player.steer+=(st-player.steer)*Math.min(1,dt*10);}
/* ---------- AI ---------- */
function aiControl(t,dt){var ai=t.ai;if(t.wrecked){t.throttle=0;t.steer=0;return;}
var fx=Math.cos(t.h),fz=-Math.sin(t.h),vf=t.vx*fx+t.vz*fz;
ai.retarget-=dt;if(ai.retarget<=0||!ai.target||ai.target.wrecked){ai.retarget=rand(2,5);
if(Math.random()<ai.aggr)ai.target=player;else{var opts=rivals.filter(function(o){return o!==t&&!o.wrecked;});ai.target=opts.length?opts[Math.floor(Math.random()*opts.length)]:player;}}
var tg=ai.target,lead=0.6,tx=tg.x+tg.vx*lead,tz=tg.z+tg.vz*lead,dx=tx-t.x,dz=tz-t.z,dist=Math.sqrt(dx*dx+dz*dz)||1;dx/=dist;dz/=dist;
if(dist>8&&dist<24){dx+=-dz*ai.side*0.45;dz+=dx*ai.side*0.45;}
for(var i=0;i<rivals.length;i++){var o=rivals[i];if(o===t||o.wrecked)continue;var ox=t.x-o.x,oz=t.z-o.z,od=Math.sqrt(ox*ox+oz*oz);if(od<10&&od>0.01){var w=(10-od)/10*1.6;dx+=ox/od*w;dz+=oz/od*w;}}
var m=7;if(t.x>W-m)dx-=(t.x-(W-m))*0.5;if(t.x<-W+m)dx+=((-W+m)-t.x)*0.5;if(t.z>D-m)dz-=(t.z-(D-m))*0.5;if(t.z<-D+m)dz+=((-D+m)-t.z)*0.5;
var desired=Math.atan2(-dz,dx),diff=wrap(desired-t.h),steer,th;
if(ai.reverseT>0){ai.reverseT-=dt;th=-1;steer=diff>0?-1:1;}
else{steer=clamp(diff*1.8,-1,1);th=1;if(Math.abs(diff)>2.4&&vf<4)ai.reverseT=0.9;}
if(vf<1.2&&th>0)ai.stuck+=dt;else ai.stuck=Math.max(0,ai.stuck-dt*2);
if(ai.stuck>1.2){ai.reverseT=1.1;ai.stuck=0;}
t.throttle=th;t.steer+=(steer-t.steer)*Math.min(1,dt*8);t.brake=false;}
/* ---------- physics ---------- */
function stepTruck(t,dt){
var fx=Math.cos(t.h),fz=-Math.sin(t.h),sx=-fz,sz=fx,vf=t.vx*fx+t.vz*fz,vs=t.vx*sx+t.vz*sz;
if(t.wrecked){t.throttle=0;t.steer=0;}
if(!t.air){
if(t.throttle>0)vf+=t.accel*dt*Math.max(0.15,1-Math.max(0,vf)/t.maxSpeed);
else if(t.throttle<0){if(vf>0.5)vf-=t.accel*1.7*dt;else vf=Math.max(vf-t.accel*0.7*dt,-9);}
vs*=Math.exp(-dt*(t.brake?2.5:9));vf*=Math.exp(-dt*(t.brake?1.8:0.45));if(t.wrecked)vf*=Math.exp(-dt*2.5);
t.h+=t.steer*2.3*clamp(vf/6,-1,1)*(t.brake?1.35:1)*dt;
}else t.h+=t.steer*0.5*dt;
fx=Math.cos(t.h);fz=-Math.sin(t.h);sx=-fz;sz=fx;t.vx=vf*fx+vs*sx;t.vz=vf*fz+vs*sz;
var g0=groundH(t.x,t.z);t.x+=t.vx*dt;t.z+=t.vz*dt;
var hit=0;
if(t.x>W-t.r){t.x=W-t.r;if(t.vx>0){hit=t.vx;t.vx=-t.vx*0.35;}}
if(t.x<-W+t.r){t.x=-W+t.r;if(t.vx<0){hit=-t.vx;t.vx=-t.vx*0.35;}}
if(t.z>D-t.r){t.z=D-t.r;if(t.vz>0){hit=t.vz;t.vz=-t.vz*0.35;}}
if(t.z<-D+t.r){t.z=-D+t.r;if(t.vz<0){hit=-t.vz;t.vz=-t.vz*0.35;}}
if(hit>4){t.hp-=hit*1.2;t.flash=1;sparks.emit(t.x,t.y+1,t.z,Math.round(hit*1.5),4,3);sndCrash(hit*0.6);if(t.player)state.shake+=hit*0.03;}
var g1=groundH(t.x,t.z);
if(!t.air){var vyPrev=t.vy,yPred=t.y+vyPrev*dt-0.5*GRAV*dt*dt;
if(Math.abs(vf)>6&&vyPrev>0.5&&yPred>g1+0.04){t.air=true;t.airT=0;t.vy=vyPrev-GRAV*dt;t.y=yPred;}else{t.y=g1;t.vy=(g1-g0)/dt;}}
else{t.airT+=dt;t.vy-=GRAV*dt;t.y+=t.vy*dt;
if(t.y<=g1){t.y=g1;t.air=false;var imp=-t.vy;t.vy=0;if(imp>5){dust.emit(t.x,t.y+0.3,t.z,Math.round(imp*2),3,2);if(t.player){state.shake+=imp*0.02;if(t.airT>0.7)addScore(50,'BIG AIR +50');}}}}
}
function collideTrucks(){
for(var i=0;i<trucks.length;i++)for(var j=i+1;j<trucks.length;j++){var a=trucks[i],b=trucks[j],dx=b.x-a.x,dz=b.z-a.z,d=Math.sqrt(dx*dx+dz*dz),min=a.r+b.r;
if(d<min&&d>0.001){var nx=dx/d,nz=dz/d,ov=(min-d)/2;a.x-=nx*ov;a.z-=nz*ov;b.x+=nx*ov;b.z+=nz*ov;
var vn=(b.vx-a.vx)*nx+(b.vz-a.vz)*nz;
if(vn<0){var jimp=-(1+0.6)*vn/2;a.vx-=jimp*nx;a.vz-=jimp*nz;b.vx+=jimp*nx;b.vz+=jimp*nz;var imp=-vn;
if(imp>3){var aTo=a.vx*nx+a.vz*nz,bTo=-(b.vx*nx+b.vz*nz),ram=aTo>=bTo?a:b,vic=ram===a?b:a,dmg=imp*3;
vic.hp-=dmg;ram.hp-=dmg*0.35;vic.lastHit=ram;vic.lastHitT=1.5;a.flash=b.flash=1;
var mx=(a.x+b.x)/2,mz=(a.z+b.z)/2;sparks.emit(mx,Math.max(a.y,b.y)+1.2,mz,Math.round(imp*3),6,5);
state.excite=Math.min(1,state.excite+imp*0.04);sndCrash(imp);
if(ram.player){var pts=Math.round(imp*12);addScore(pts,'RAM +'+pts);state.shake+=imp*0.02;}
else if(vic.player)state.shake+=imp*0.05;}}}}
}
function collideObstacles(){
for(var i=0;i<trucks.length;i++){var t=trucks[i];for(var k=0;k<obstacles.length;k++){var o=obstacles[k];if(o.crushed)continue;
var dx=t.x-o.x,dz=t.z-o.z,d=Math.sqrt(dx*dx+dz*dz),min=t.r+o.r;if(d>=min||d<0.001)continue;var nx=dx/d,nz=dz/d,vn=t.vx*nx+t.vz*nz;
if(o.type==='tire'){var push=Math.max(3,-vn);o.vx=-nx*push*0.9+t.vx*0.3;o.vz=-nz*push*0.9+t.vz*0.3;o.vy=push*0.35;o.x-=nx*(min-d);o.z-=nz*(min-d);t.vx*=0.97;t.vz*=0.97;dust.emit(o.x,groundH(o.x,o.z)+0.5,o.z,4,2,2);}
else if(o.type==='barrier'){t.x+=nx*(min-d);t.z+=nz*(min-d);if(vn<0){t.vx-=1.5*vn*nx;t.vz-=1.5*vn*nz;var imp=-vn;if(imp>4){t.hp-=imp*1.2;t.flash=1;sparks.emit(t.x-nx*2,t.y+0.8,t.z-nz*2,Math.round(imp*1.5),4,3);sndCrash(imp*0.7);if(t.player)state.shake+=imp*0.03;}}}
else{if(vn<-7){o.crushed=true;t.vx*=0.75;t.vz*=0.75;sparks.emit(o.x,groundH(o.x,o.z)+1,o.z,25,5,5);dust.emit(o.x,groundH(o.x,o.z)+0.5,o.z,15,3,2);sndCrash(12);state.excite=Math.min(1,state.excite+0.3);if(t.player){addScore(150,'CRUSHED +150');state.shake+=0.4;}}
else{t.x+=nx*(min-d);t.z+=nz*(min-d);if(vn<0){t.vx-=1.2*vn*nx;t.vz-=1.2*vn*nz;}}}}}
}
function stepObstacles(dt){obstacles.forEach(function(o){
if(o.type==='tire'){o.x+=o.vx*dt;o.z+=o.vz*dt;var f=Math.exp(-dt*1.3);o.vx*=f;o.vz*=f;
if(o.x>W-1){o.x=W-1;o.vx=-o.vx*0.5;}if(o.x<-W+1){o.x=-W+1;o.vx=-o.vx*0.5;}if(o.z>D-1){o.z=D-1;o.vz=-o.vz*0.5;}if(o.z<-D+1){o.z=-D+1;o.vz=-o.vz*0.5;}
o.vy-=25*dt;o.yoff+=o.vy*dt;if(o.yoff<0){o.yoff=0;o.vy=Math.abs(o.vy)>1?-o.vy*0.4:0;}o.mesh.rotation.y+=(Math.abs(o.vx)+Math.abs(o.vz))*dt*0.4;}
if(o.type==='junk'&&o.crushed)o.mesh.scale.y+=(0.3-o.mesh.scale.y)*Math.min(1,dt*8);
o.mesh.position.set(o.x,groundH(o.x,o.z)+o.yoff,o.z);});}
function wreck(t){t.wrecked=true;t.wreckT=4;t.mesh.userData.bodyMat.color.setHex(0x33312e);t.mesh.userData.flag.visible=false;t.mesh.userData.pole.visible=false;
sparks.emit(t.x,t.y+1.5,t.z,60,7,8);smoke.emit(t.x,t.y+2,t.z,20,1,3);state.excite=1;sndCrash(20);
if(t.player){addScore(-250,'YOU GOT WRECKED −250');state.shake+=1;}
else if(t.lastHit===player&&t.lastHitT>0)addScore(500,'WRECKED '+t.def.name+'! +500');else showMsg(t.def.name+' IS OUT');}
function checkWrecks(dt){trucks.forEach(function(t){t.lastHitT-=dt;t.flash=Math.max(0,t.flash-dt*3);
if(!t.wrecked&&t.hp<=0)wreck(t);
if(t.wrecked){t.wreckT-=dt;if(Math.random()<0.5)smoke.emit(t.x,t.y+2,t.z,1,0.4,3);if(t.wreckT<=0)respawn(t);}});}
/* ---------- score / HUD ---------- */
var msgEl=$('msg');
function showMsg(txt){msgEl.textContent=txt;msgEl.style.opacity=1;state.msgT=1.5;}
function addScore(n,txt){state.score=Math.max(0,state.score+n);if(txt)showMsg(txt);}
function fmtScore(s){return Math.round(s).toLocaleString();}
function updateHUD(){var t=Math.max(0,state.time),m=Math.floor(t/60),s=Math.ceil(t-m*60);if(s===60){m++;s=0;}$('clock').textContent=m+':'+(s<10?'0':'')+s;
$('scoreVal').textContent=fmtScore(state.score);$('hpFill').style.width=clamp(player.hp,0,100)+'%';$('hpFill').style.background=player.hp>40?'#4cf':'#f54';}
/* ---------- visuals ---------- */
var _n=new THREE.Vector3(),_f=new THREE.Vector3(),_r=new THREE.Vector3(),_m=new THREE.Matrix4(),_q=new THREE.Quaternion(),_qr=new THREE.Quaternion();
function syncTruck(t,dt){var u=t.mesh.userData;t.mesh.position.set(t.x,t.y+1.05,t.z);
if(t.air)_n.set(0,1,0);else groundN(t.x,t.z,_n);
_f.set(Math.cos(t.h),t.air?clamp(t.vy*0.04,-0.5,0.5):0,-Math.sin(t.h));_f.addScaledVector(_n,-_f.dot(_n)).normalize();_r.crossVectors(_f,_n).normalize();
_m.makeBasis(_f,_n,_r);_q.setFromRotationMatrix(_m);if(t.wrecked){_qr.setFromAxisAngle(_f,0.5);_q.premultiply(_qr);}
t.mesh.quaternion.slerp(_q,1-Math.exp(-dt*12));
var fx=Math.cos(t.h),fz=-Math.sin(t.h),vf=t.vx*fx+t.vz*fz;
for(var i=0;i<u.wheels.length;i++){var w=u.wheels[i];w.rotation.y=w.userData.front?t.steer*0.45:0;w.userData.spin.rotation.z-=vf*dt/1.05;}
u.bodyMat.emissive.setRGB(t.flash*0.9,t.flash*0.15,0);u.flag.rotation.y=Math.sin(performance.now()*0.01+t.x)*0.3;}
var camPos=new THREE.Vector3(0,40,90),camLook=new THREE.Vector3(0,0,0),_cp=new THREE.Vector3(),_cl=new THREE.Vector3();
function updateCamera(dt,elapsed){
if(!state.started){_cp.set(Math.cos(elapsed*0.15)*75,30,Math.sin(elapsed*0.15)*75);_cl.set(0,2,0);camPos.lerp(_cp,1-Math.exp(-dt*2));camLook.lerp(_cl,1-Math.exp(-dt*2));}
else{var p=player,fx=Math.cos(p.h),fz=-Math.sin(p.h);_cp.set(p.x-fx*12,p.y+6,p.z-fz*12);
var cx=clamp(_cp.x,-W-1,W+1),cz=clamp(_cp.z,-D-1,D+1);_cp.y+=(Math.abs(_cp.x-cx)+Math.abs(_cp.z-cz))*0.7;_cp.x=cx;_cp.z=cz;_cp.y=Math.max(_cp.y,groundH(cx,cz)+2.5);
camPos.lerp(_cp,1-Math.exp(-dt*3.5));_cl.set(p.x+fx*4,p.y+1.5,p.z+fz*4);camLook.lerp(_cl,1-Math.exp(-dt*6));}
camera.position.copy(camPos);if(state.shake>0.01){camera.position.x+=rand(-1,1)*state.shake;camera.position.y+=rand(-1,1)*state.shake;camera.position.z+=rand(-1,1)*state.shake;}
camera.lookAt(camLook);}
/* ---------- game flow ---------- */
function reset(){DEMO=location.hash==='#demo';ROUND_TIME=DEMO?20:120;START_SCORE=DEMO?5000:0;
state.time=ROUND_TIME;state.score=START_SCORE;state.over=false;state.excite=0;state.shake=0;state.msgT=0;msgEl.style.opacity=0;
trucks.forEach(function(t){placeTruck(t,t.def.spawn[0],t.def.spawn[1]);t.steer=0;t.throttle=0;t.ai.target=null;t.ai.retarget=rand(0,2);t.ai.stuck=0;t.ai.reverseT=0;});
resetObstacles();$('over').classList.add('hidden');$('arenaSub').textContent='Fremont Fairgrounds · Monster Rally'+(DEMO?' · DEMO':'');}
function startGame(){if(state.started)return;initAudio();state.started=true;$('start').classList.add('hidden');last=performance.now();showMsg('GO!');}
function restart(){reset();state.photo=false;$('photo').classList.add('hidden');last=performance.now();showMsg('GO!');}
function endGame(){state.over=true;$('overScore').textContent=fmtScore(state.score);$('over').classList.remove('hidden');updateAudio(false);}
function togglePhoto(){state.photo=!state.photo;$('photo').classList.toggle('hidden',!state.photo);$('photoScore').textContent=fmtScore(state.score);updateAudio(!state.photo);last=performance.now();}
function simulate(dt){
state.time-=dt;if(state.time<=0){state.time=0;endGame();return;}
readInput(dt);rivals.forEach(function(t){aiControl(t,dt);});
trucks.forEach(function(t){stepTruck(t,dt);});
collideTrucks();collideObstacles();stepObstacles(dt);checkWrecks(dt);
state.excite=Math.max(0,state.excite-dt*0.45);state.shake*=Math.exp(-dt*6);
state.msgT-=dt;if(state.msgT<0)msgEl.style.opacity=0;}
reset();
addEventListener('resize',function(){camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight);if(state.photo)renderer.render(scene,camera);});
var last=performance.now(),elapsed=0;
function loop(now){requestAnimationFrame(loop);
var dt=Math.min(0.033,Math.max(0.001,(now-last)/1000));last=now;
if(state.photo)return;
elapsed+=dt;
if(state.started&&!state.over)simulate(dt);
trucks.forEach(function(t){syncTruck(t,dt);});
sparks.update(dt);dust.update(dt);smoke.update(dt);
updateCrowd(elapsed);updateCamera(dt,elapsed);updateHUD();updateAudio(state.started&&!state.over);
renderer.render(scene,camera);}
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
(10−d)/10·1.6), a flanking offset perpendicular to the target direction whose sign alternates per truck (ai.side = ±1), and a soft wall-repulsion inside a 7 m margin. Targets are also staggered — each rival re-rolls every 2–5 s between the player and a random living rival with its own aggression (0.4–0.9) — and top speeds differ (18–21 m/s), so they arrive at different times from different sides instead of stacking on one bearing. A stuck timer (speed < 1.2 for 1.2 s) or facing-away check kicks a 1 s reverse with inverted steer so a pile-up unpacks itself.reset() runs DEMO = location.hash === '#demo'; ROUND_TIME = DEMO ? 20 : 120; START_SCORE = DEMO ? 5000 : 0; and then state.time = ROUND_TIME; state.score = START_SCORE;. Those are the same two fields simulate() decrements and addScore() mutates, endGame() reads and the HUD formats — there is no separate display value, so a ram in demo mode shows 5,000 + pts and the buzzer fires at 20 s of real simulated time.P flips state.photo; the very first line of the rAF callback after the frame is if (state.photo) return;, which skips simulate() (input, AI, integration, collisions, timer), the truck/particle/crowd sync, camera, and renderer.render. Both the physics step and the render loop stop; the canvas keeps its last frame under the card, the engine/crowd gain is zeroed, and last is re-stamped on resume so no time is banked.d < rA + rB I resolve penetration symmetrically along the contact normal n, then compute the relative normal velocity vn = (vB − vA)·n; if approaching, an equal-mass restitution impulse j = −(1+0.6)·vn/2 is applied as ±j·n to both velocity vectors (the velocity is stored as a world vector and decomposed into forward/lateral each step, so a side hit becomes a genuine slide). |vn| is the impact energy: damage 3|vn| to the victim and 1.05|vn| to the rammer, where the rammer is whichever truck has the larger closing speed along n; player-as-rammer scores 12|vn|, and a rival driven to 0 HP within 1.5 s of a player hit pays 500. Barriers are static circles that reflect the truck's normal velocity with e = 0.5; tires take the truck's velocity as a push impulse plus a vertical hop and are integrated with friction and wall bounce; junk cars crush (flag + y-scale) when vn < −7, otherwise they act as soft barriers.