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.CYBERSLAM at the Tungsten Dome — five stainless Cybertruck monsters brawl across a floodlit dirt bowl of launch mounds, crusher-car piles, tire towers and burning barrels, while ~2,500 instanced fans pack the stands all the way around.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>CYBERSLAM — Tungsten Dome Rally</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#05070d;
font-family:"Segoe UI","Helvetica Neue",Arial,sans-serif;user-select:none;-webkit-user-select:none}
canvas{display:block}
#hud{position:fixed;top:0;left:0;right:0;display:flex;justify-content:space-between;
align-items:flex-start;padding:14px 18px;pointer-events:none;z-index:5}
.panel{background:rgba(8,14,24,.78);border:1px solid rgba(69,243,255,.35);
padding:8px 18px;clip-path:polygon(0 0,100% 0,calc(100% - 16px) 100%,0 100%)}
#arenaName{font-size:20px;font-weight:900;font-style:italic;letter-spacing:2px;color:#eaf9ff;
text-shadow:0 0 14px rgba(69,243,255,.9)}
#rallyName{font-size:10px;font-weight:700;letter-spacing:5px;color:#ff3fd4;margin-top:2px}
#clockWrap{text-align:center}
#clockLabel{font-size:9px;letter-spacing:4px;color:#8aa0b8}
#clock{font-size:36px;font-weight:900;font-style:italic;color:#fff;line-height:1;
font-variant-numeric:tabular-nums;text-shadow:0 0 16px rgba(255,255,255,.55)}
#scoreLabel{font-size:9px;letter-spacing:4px;color:#8aa0b8;text-align:right}
#score{font-size:30px;font-weight:900;font-style:italic;color:#ffd25d;line-height:1.05;
text-align:right;font-variant-numeric:tabular-nums;text-shadow:0 0 14px rgba(255,210,93,.7)}
#hint{position:fixed;bottom:12px;left:50%;transform:translateX(-50%);z-index:5;
font-size:11px;letter-spacing:3px;color:#9fb4c8;background:rgba(8,14,24,.6);
border:1px solid rgba(69,243,255,.2);padding:6px 16px;pointer-events:none;white-space:nowrap}
#hint b{color:#45f3ff}
#hullWrap{position:fixed;bottom:12px;left:18px;z-index:5;pointer-events:none;
font-size:10px;letter-spacing:3px;color:#9fb4c8}
#hullBar{width:170px;height:9px;background:rgba(255,255,255,.08);margin-top:4px;
border:1px solid rgba(69,243,255,.3);transform:skewX(-18deg)}
#hullFill{height:100%;width:100%;background:linear-gradient(90deg,#ff3fd4,#45f3ff);transition:width .15s}
#banners{position:fixed;top:110px;left:0;right:0;display:flex;flex-direction:column;
align-items:center;gap:6px;pointer-events:none;z-index:6}
.banner{font-size:26px;font-weight:900;font-style:italic;letter-spacing:2px;color:#fff;
text-shadow:0 0 18px rgba(69,243,255,.9);animation:bpop 1.6s ease-out forwards}
.banner.good{color:#ffd25d;text-shadow:0 0 18px rgba(255,210,93,.9)}
.banner.bad{color:#ff4d5e;text-shadow:0 0 18px rgba(255,77,94,.9)}
@keyframes bpop{0%{opacity:0;transform:scale(.6)}14%{opacity:1;transform:scale(1.08)}
30%{transform:scale(1)}75%{opacity:1}100%{opacity:0;transform:translateY(-18px)}}
.card{position:fixed;inset:0;display:none;align-items:center;justify-content:center;
z-index:10;background:rgba(3,6,12,.5);backdrop-filter:blur(3px)}
.cardInner{background:rgba(8,13,24,.92);border:1px solid #45f3ff;box-shadow:0 0 40px rgba(69,243,255,.35);
padding:34px 56px;text-align:center;clip-path:polygon(0 0,100% 0,100% calc(100% - 22px),
calc(100% - 22px) 100%,0 100%)}
.cArena{font-size:34px;font-weight:900;font-style:italic;letter-spacing:3px;color:#eaf9ff;
text-shadow:0 0 20px rgba(69,243,255,.9)}
.cRally{font-size:11px;letter-spacing:6px;color:#ff3fd4;margin-top:4px}
.cScore{margin-top:18px;font-size:13px;letter-spacing:4px;color:#8aa0b8}
.cScore b{display:block;font-size:44px;font-style:italic;color:#ffd25d;
text-shadow:0 0 18px rgba(255,210,93,.8)}
.cHint{margin-top:16px;font-size:11px;letter-spacing:3px;color:#9fb4c8}
#fallback{position:fixed;inset:0;display:none;align-items:center;justify-content:center;
color:#eaf9ff;font-size:15px;letter-spacing:1px;text-align:center;z-index:20;padding:30px}
</style>
</head>
<body>
<div id="hud">
<div class="panel"><div id="arenaName">THE TUNGSTEN DOME</div><div id="rallyName">CYBERSLAM RALLY</div></div>
<div class="panel" id="clockWrap"><div id="clockLabel">TIME</div><div id="clock">1:30</div></div>
<div class="panel"><div id="scoreLabel">SCORE</div><div id="score">0</div></div>
</div>
<div id="hullWrap">HULL<div id="hullBar"><div id="hullFill"></div></div></div>
<div id="hint"><b>W A S D</b> / <b>ARROWS</b> — DRIVE · <b>P</b> — PHOTO MODE</div>
<div id="banners"></div>
<div class="card" id="photoCard"><div class="cardInner">
<div class="cArena">THE TUNGSTEN DOME</div>
<div class="cRally">CYBERSLAM RALLY</div>
<div class="cScore">SCORE<b id="photoScore">0</b></div>
<div class="cHint">PHOTO MODE — PRESS P TO RESUME</div>
</div></div>
<div class="card" id="endCard"><div class="cardInner">
<div class="cArena">TIME UP</div>
<div class="cRally">THE TUNGSTEN DOME GOES QUIET</div>
<div class="cScore">FINAL SCORE<b id="endScore">0</b></div>
<div class="cHint" id="endTk"></div>
<div class="cHint">PRESS R TO RUN IT BACK</div>
</div></div>
<div id="fallback">CYBERSLAM needs three.js from the CDN and WebGL to run.<br>Check your connection / graphics drivers and reload.</div>
<script src="https://unpkg.com/three@0.149.0/build/three.min.js"></script>
<script>
(function(){
'use strict';
if(!window.THREE){document.getElementById('fallback').style.display='flex';return;}
/* ============ core state ============ */
var DEMO = (location.hash === '#demo');
var ARENA = 'THE TUNGSTEN DOME';
var score = 0, timeLeft = 90, takedowns = 0;
var state = 'play', photo = false;
var simTime = 0, shake = 0;
/* ============ renderer / scene ============ */
var renderer;
try{
renderer = new THREE.WebGLRenderer({antialias:true});
}catch(e){document.getElementById('fallback').style.display='flex';return;}
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.1;
document.body.appendChild(renderer.domElement);
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x070b16);
scene.fog = new THREE.FogExp2(0x070b16, 0.0035);
var camera = new THREE.PerspectiveCamera(62, window.innerWidth/window.innerHeight, 0.1, 400);
window.addEventListener('resize',function(){
camera.aspect = window.innerWidth/window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth,window.innerHeight);
});
/* lights */
scene.add(new THREE.HemisphereLight(0x8fb6ff, 0x241c14, 0.55));
var sun = new THREE.DirectionalLight(0xfff2dd, 1.05);
sun.position.set(40,70,25);
sun.castShadow = true;
sun.shadow.mapSize.set(1024,1024);
sun.shadow.camera.left=-75; sun.shadow.camera.right=75;
sun.shadow.camera.top=75; sun.shadow.camera.bottom=-75;
sun.shadow.camera.near=10; sun.shadow.camera.far=180;
sun.shadow.camera.updateProjectionMatrix();
scene.add(sun);
var pl1 = new THREE.PointLight(0x45f3ff,0.55,150); pl1.position.set(-50,18,-50); scene.add(pl1);
var pl2 = new THREE.PointLight(0xff3fd4,0.45,150); pl2.position.set(50,18,50); scene.add(pl2);
/* ============ terrain heightfield (artificial hills) ============ */
var mounds = [
{x:-22,z:-12,r:9,h:3.2},
{x:16,z:-24,r:8,h:2.8},
{x:8,z:22,r:10,h:3.6},
{x:-30,z:16,r:7,h:2.4}
];
function groundH(x,z){
var y=0;
for(var i=0;i<mounds.length;i++){
var m=mounds[i], dx=x-m.x, dz=z-m.z, s2=2*Math.pow(m.r*0.55,2);
y += m.h*Math.exp(-(dx*dx+dz*dz)/s2);
}
return y;
}
/* ============ dirt texture (procedural, no assets) ============ */
function makeDirtTexture(){
var c=document.createElement('canvas'); c.width=c.height=512;
var g=c.getContext('2d');
g.fillStyle='#7d6746'; g.fillRect(0,0,512,512);
for(var i=0;i<9000;i++){
var v=Math.random();
g.fillStyle = v<0.5 ? 'rgba(40,30,18,'+(Math.random()*0.25)+')'
: 'rgba(220,200,160,'+(Math.random()*0.14)+')';
g.fillRect(Math.random()*512, Math.random()*512, 1+Math.random()*2.5, 1+Math.random()*2.5);
}
g.strokeStyle='rgba(30,22,12,0.16)'; g.lineWidth=7;
for(i=0;i<10;i++){
g.beginPath();
g.arc(256,256, 40+Math.random()*200, Math.random()*6.3, Math.random()*6.3);
g.stroke();
}
var t=new THREE.CanvasTexture(c); t.encoding=THREE.sRGBEncoding;
return t;
}
/* ============ stadium ============ */
var floor = new THREE.Mesh(
new THREE.CircleGeometry(62,64).rotateX(-Math.PI/2),
new THREE.MeshStandardMaterial({map:makeDirtTexture(), roughness:1})
);
floor.receiveShadow=true; scene.add(floor);
/* painted rings */
var ring1 = new THREE.Mesh(new THREE.RingGeometry(9.6,10.3,64).rotateX(-Math.PI/2),
new THREE.MeshBasicMaterial({color:0x45f3ff,transparent:true,opacity:0.3}));
ring1.position.y=0.05; scene.add(ring1);
var ring2 = new THREE.Mesh(new THREE.RingGeometry(24,24.4,64).rotateX(-Math.PI/2),
new THREE.MeshBasicMaterial({color:0xff3fd4,transparent:true,opacity:0.16}));
ring2.position.y=0.05; scene.add(ring2);
/* inner wall + neon lip */
var wall = new THREE.Mesh(new THREE.CylinderGeometry(60.5,60.5,2.4,64,1,true),
new THREE.MeshStandardMaterial({color:0x2a3140,roughness:0.85,side:THREE.DoubleSide}));
wall.position.y=1.2; scene.add(wall);
var lip = new THREE.Mesh(new THREE.TorusGeometry(60.5,0.12,8,80).rotateX(Math.PI/2),
new THREE.MeshBasicMaterial({color:0x45f3ff}));
lip.position.y=2.45; scene.add(lip);
/* bleacher frustums, 5 tiers all the way round */
for(var ti=0;ti<5;ti++){
var fr = new THREE.Mesh(
new THREE.CylinderGeometry(63+(ti+1)*7, 63+ti*7, 4, 64),
new THREE.MeshStandardMaterial({color: ti%2?0x1f2734:0x242e40, roughness:0.9}));
fr.position.y = ti*4+2; scene.add(fr);
}
var backWall = new THREE.Mesh(new THREE.CylinderGeometry(99,99,7,64,1,true),
new THREE.MeshStandardMaterial({color:0x151b26,roughness:0.9,side:THREE.DoubleSide}));
backWall.position.y=23.5; scene.add(backWall);
var roofRing = new THREE.Mesh(new THREE.TorusGeometry(99,0.55,8,90).rotateX(Math.PI/2),
new THREE.MeshBasicMaterial({color:0x45f3ff}));
roofRing.position.y=27; scene.add(roofRing);
/* floodlight towers */
for(ti=0;ti<4;ti++){
var ta = ti*Math.PI/2 + Math.PI/4;
var tg = new THREE.Group();
var pole = new THREE.Mesh(new THREE.CylinderGeometry(0.5,0.8,28,8),
new THREE.MeshStandardMaterial({color:0x2c3442,roughness:0.8}));
pole.position.y=14; tg.add(pole);
var head = new THREE.Mesh(new THREE.BoxGeometry(6,2.4,1),
new THREE.MeshBasicMaterial({color:0xf8fbff}));
head.position.y=28.5; tg.add(head);
tg.position.set(Math.sin(ta)*96,0,Math.cos(ta)*96);
tg.lookAt(0,20,0);
scene.add(tg);
}
/* ============ the crowd ============ */
var up = new THREE.Vector3(0,1,0);
var _m4=new THREE.Matrix4(), _q=new THREE.Quaternion(), _p=new THREE.Vector3(), _s=new THREE.Vector3();
var _c=new THREE.Color();
var palette=[0xff5d6c,0x5db2ff,0xffd25d,0x6fe08a,0xff9ad5,0xf2f2f2,0xb98aff,0xff9a5d,0x5dffd2];
function standPos(tier){
var r0=63+tier*7, rr=r0+0.8+Math.random()*5.6, a=Math.random()*Math.PI*2;
var y=tier*4 + ((rr-r0)/7)*4 + 0.55;
return {a:a,r:rr,y:y};
}
var CROWD_N=2300;
var crowd = new THREE.InstancedMesh(new THREE.BoxGeometry(0.5,0.95,0.45),
new THREE.MeshLambertMaterial(), CROWD_N);
for(var ci=0;ci<CROWD_N;ci++){
var sp=standPos(Math.floor(Math.random()*5));
_p.set(Math.sin(sp.a)*sp.r, sp.y, Math.cos(sp.a)*sp.r);
_q.setFromAxisAngle(up, sp.a+Math.random());
var sc=0.8+Math.random()*0.45;
_s.set(sc,0.8+Math.random()*0.5,sc);
_m4.compose(_p,_q,_s);
crowd.setMatrixAt(ci,_m4);
_c.setHex(palette[(Math.random()*palette.length)|0]).multiplyScalar(0.7+Math.random()*0.5);
crowd.setColorAt(ci,_c);
}
scene.add(crowd);
var WAVE_N=220, wavers=[], waverMesh;
waverMesh = new THREE.InstancedMesh(new THREE.BoxGeometry(0.5,1.0,0.45),
new THREE.MeshLambertMaterial(), WAVE_N);
for(ci=0;ci<WAVE_N;ci++){
var wp=standPos(Math.floor(Math.random()*5));
wavers.push({a:wp.a,r:wp.r,y:wp.y,phase:Math.random()*6.28});
_c.setHex(palette[(Math.random()*palette.length)|0]);
waverMesh.setColorAt(ci,_c);
}
scene.add(waverMesh);
function updateCrowd(){
for(var i=0;i<WAVE_N;i++){
var w=wavers[i];
var y=w.y + 0.2 + 0.28*Math.sin(simTime*3.1+w.phase);
_p.set(Math.sin(w.a)*w.r, y, Math.cos(w.a)*w.r);
_q.setFromAxisAngle(up,w.a);
_s.set(1,1,1);
_m4.compose(_p,_q,_s);
waverMesh.setMatrixAt(i,_m4);
}
waverMesh.instanceMatrix.needsUpdate=true;
}
/* ============ mounds (visual match to heightfield) ============ */
mounds.forEach(function(m){
var w=m.r*3.4, s2=2*Math.pow(m.r*0.55,2);
var geo=new THREE.PlaneGeometry(w,w,22,22); geo.rotateX(-Math.PI/2);
var pos=geo.attributes.position;
for(var i=0;i<pos.count;i++){
var x=pos.getX(i), z=pos.getZ(i);
pos.setY(i, m.h*Math.exp(-(x*x+z*z)/s2));
}
geo.computeVertexNormals();
var mesh=new THREE.Mesh(geo,new THREE.MeshStandardMaterial({color:0x6e5a3c,roughness:1}));
mesh.position.set(m.x,0.02,m.z);
mesh.receiveShadow=true; mesh.castShadow=true;
scene.add(mesh);
});
/* ============ obstacles: 4 collidable types + hills ============ */
var obstacles=[], barrelMats=[];
function addObstacle(x,z,r){ obstacles.push({x:x,z:z,r:r}); }
function carPile(x,z,rot){
var g=new THREE.Group();
var cols=[0x8a4a2f,0x5a6a72,0x7a3a3a];
for(var i=0;i<3;i++){
var b=new THREE.Mesh(new THREE.BoxGeometry(3.4-i*0.5,0.7,1.7-i*0.2),
new THREE.MeshStandardMaterial({color:cols[i],roughness:0.8,metalness:0.3}));
b.position.y=0.35+i*0.68; b.rotation.y=(Math.random()-0.5)*0.7; b.castShadow=true;
g.add(b);
}
g.position.set(x,groundH(x,z),z); g.rotation.y=rot; scene.add(g);
addObstacle(x,z,3.1);
}
function tireStack(x,z){
var g=new THREE.Group();
var mat=new THREE.MeshStandardMaterial({color:0x14161a,roughness:0.95});
for(var i=0;i<4;i++){
var t=new THREE.Mesh(new THREE.CylinderGeometry(1.1,1.1,0.45,12),mat);
t.position.y=0.23+i*0.45; t.rotation.y=Math.random(); t.castShadow=true; g.add(t);
}
g.position.set(x,groundH(x,z),z); scene.add(g);
addObstacle(x,z,2.2);
}
function block(x,z,rot){
var g=new THREE.Group();
var b1=new THREE.Mesh(new THREE.BoxGeometry(3.2,1.5,1.2),
new THREE.MeshStandardMaterial({color:0x9aa0a8,roughness:0.9}));
b1.position.y=0.75; b1.castShadow=true; g.add(b1);
var b2=new THREE.Mesh(new THREE.BoxGeometry(1.6,1.0,1.1),
new THREE.MeshStandardMaterial({color:0x848a92,roughness:0.9}));
b2.position.set(0.4,1.95,0); b2.rotation.y=0.4; b2.castShadow=true; g.add(b2);
g.position.set(x,groundH(x,z),z); g.rotation.y=rot; scene.add(g);
addObstacle(x,z,2.5);
}
function barrels(x,z){
var g=new THREE.Group();
for(var i=0;i<2;i++){
var mat=new THREE.MeshStandardMaterial({color:0x663322,roughness:0.6,metalness:0.4,
emissive:0xff5522,emissiveIntensity:0.8});
barrelMats.push(mat);
var b=new THREE.Mesh(new THREE.CylinderGeometry(0.55,0.55,1.1,10),mat);
b.position.set(i*0.9-0.45,0.55,(i%2)*0.4-0.2); b.castShadow=true; g.add(b);
var flame=new THREE.Mesh(new THREE.ConeGeometry(0.3,0.8,6),
new THREE.MeshBasicMaterial({color:0xffa040,transparent:true,opacity:0.85}));
flame.position.set(b.position.x,1.4,b.position.z); g.add(flame);
}
g.position.set(x,groundH(x,z),z); scene.add(g);
addObstacle(x,z,1.7);
}
carPile(14,4,0.4); carPile(-12,-30,1.1); carPile(-36,-8,2.0); carPile(30,24,0.9);
tireStack(-6,34); tireStack(26,-6); tireStack(-24,30);
block(2,-14,0.5); block(36,8,1.2); block(-16,12,2.6);
barrels(6,-2); barrels(-2,-40);
/* scattered non-colliding debris */
for(var di=0;di<46;di++){
var da=Math.random()*6.28, dr=6+Math.random()*46;
var rx=Math.sin(da)*dr, rz=Math.cos(da)*dr;
var rock=new THREE.Mesh(new THREE.DodecahedronGeometry(0.22+Math.random()*0.3,0),
new THREE.MeshStandardMaterial({color:Math.random()<0.5?0x55504a:0x6a5a44,roughness:1}));
rock.position.set(rx,groundH(rx,rz)+0.15,rz);
rock.rotation.set(Math.random()*3,Math.random()*3,Math.random()*3);
scene.add(rock);
}
/* ============ trucks (exactly five) ============ */
function makeTruck(accent){
var g=new THREE.Group(), tilt=new THREE.Group(); g.add(tilt);
var bodyMat=new THREE.MeshStandardMaterial({color:0xcdd5dc,metalness:0.85,roughness:0.35,flatShading:true});
var shape=new THREE.Shape();
shape.moveTo(-2.3,0.30); shape.lineTo(-2.3,0.95); shape.lineTo(-1.85,1.05);
shape.lineTo(0.45,1.36); shape.lineTo(2.3,0.62); shape.lineTo(2.3,0.30); shape.closePath();
var bodyGeo=new THREE.ExtrudeGeometry(shape,{depth:1.9,bevelEnabled:false});
bodyGeo.translate(0,0,-0.95);
var body=new THREE.Mesh(bodyGeo,bodyMat); body.castShadow=true; tilt.add(body);
var chassis=new THREE.Mesh(new THREE.BoxGeometry(4.3,0.35,1.6),
new THREE.MeshStandardMaterial({color:0x14171c,roughness:0.9}));
chassis.position.y=0.18; tilt.add(chassis);
var glassMat=new THREE.MeshStandardMaterial({color:0x0c1218,metalness:0.7,roughness:0.25});
var gr=new THREE.Mesh(new THREE.BoxGeometry(2.3,0.06,1.5),glassMat);
gr.position.set(-0.7,1.24,0); gr.rotation.z=0.14; tilt.add(gr);
var gf=new THREE.Mesh(new THREE.BoxGeometry(1.9,0.06,1.5),glassMat);
gf.position.set(1.35,1.04,0); gf.rotation.z=-0.35; tilt.add(gf);
var strip=new THREE.Mesh(new THREE.BoxGeometry(4.0,0.12,2.0),
new THREE.MeshBasicMaterial({color:accent}));
strip.position.y=0.02; tilt.add(strip);
var bar=new THREE.Mesh(new THREE.BoxGeometry(0.1,0.14,1.5),
new THREE.MeshBasicMaterial({color:0xffffff}));
bar.position.set(2.28,0.74,0); tilt.add(bar);
var tail=new THREE.Mesh(new THREE.BoxGeometry(0.06,0.1,1.7),
new THREE.MeshBasicMaterial({color:0xff2233}));
tail.position.set(-2.32,1.0,0); tilt.add(tail);
tilt.position.y=0.42;
var wheelGeo=new THREE.CylinderGeometry(0.78,0.78,0.55,14); wheelGeo.rotateX(Math.PI/2);
var hubGeo=new THREE.CylinderGeometry(0.34,0.34,0.58,8); hubGeo.rotateX(Math.PI/2);
var wheelMat=new THREE.MeshStandardMaterial({color:0x111318,roughness:0.95});
var hubMat=new THREE.MeshStandardMaterial({color:accent,metalness:0.6,roughness:0.4,
emissive:accent,emissiveIntensity:0.3});
var wheels=[];
[[1.55,1.25],[1.55,-1.25],[-1.55,1.25],[-1.55,-1.25]].forEach(function(wd){
var w=new THREE.Mesh(wheelGeo,wheelMat);
w.add(new THREE.Mesh(hubGeo,hubMat));
w.position.set(wd[0],0.78,wd[1]); w.castShadow=true;
tilt.add(w); wheels.push(w);
});
return {group:g,tilt:tilt,bodyMat:bodyMat,wheels:wheels};
}
var SPAWNS=[
{x:0,z:46,player:true,accent:0x45f3ff,name:'YOU'},
{x:42,z:0,accent:0xff4d5e,name:'RAZOR'},
{x:-42,z:0,accent:0xffa93d,name:'QUAKE'},
{x:0,z:-44,accent:0x58e07a,name:'VOLT'},
{x:30,z:33,accent:0xb06bff,name:'HEX'}
];
var trucks=[], player=null;
SPAWNS.forEach(function(s,i){
var parts=makeTruck(s.accent);
scene.add(parts.group);
var t={
group:parts.group, tilt:parts.tilt, bodyMat:parts.bodyMat, wheels:parts.wheels,
pos:parts.group.position, vel:new THREE.Vector3(),
heading:Math.atan2(-s.x,-s.z),
hp:100, wrecked:false, isPlayer:!!s.player, name:s.name,
homeAngle:Math.atan2(s.x,s.z),
target:null, aiTimer:0, chargeT:0, chargeTarget:null,
aggroCd:3+i*2, stuckT:0, revT:0,
lastHitBy:null, smokeT:0, smokeAcc:0, respawnT:0, invulnT:0,
maxS: s.player?26:(22+i*1.1),
spawn:{x:s.x,z:s.z,h:Math.atan2(-s.x,-s.z)}
};
t.pos.set(s.x,groundH(s.x,s.z),s.z);
trucks.push(t);
if(s.player) player=t;
});
/* ============ particles ============ */
var sparks=[];
for(var si=0;si<90;si++){
var sm=new THREE.Mesh(new THREE.BoxGeometry(0.12,0.12,0.3),
new THREE.MeshBasicMaterial({color:0xffc94d}));
sm.visible=false; scene.add(sm);
sparks.push({m:sm,v:new THREE.Vector3(),life:0});
}
function spawnSparks(p,n){
for(var i=0,spawned=0;i<sparks.length&&spawned<n;i++){
var s=sparks[i];
if(s.life>0)continue;
s.m.visible=true;
s.m.position.set(p.x+(Math.random()-0.5),p.y+(Math.random()-0.5),p.z+(Math.random()-0.5));
s.v.set((Math.random()-0.5)*10,3+Math.random()*7,(Math.random()-0.5)*10);
s.m.rotation.set(Math.random()*3,Math.random()*3,Math.random()*3);
s.life=0.35+Math.random()*0.3; spawned++;
}
}
var smokes=[];
for(si=0;si<40;si++){
var km=new THREE.Mesh(new THREE.SphereGeometry(0.55,6,5),
new THREE.MeshLambertMaterial({color:0x6a6f76,transparent:true,opacity:0}));
km.visible=false; scene.add(km);
smokes.push({m:km,v:new THREE.Vector3(),life:0,max:1});
}
function spawnSmoke(p){
for(var i=0;i<smokes.length;i++){
var s=smokes[i];
if(s.life>0)continue;
s.m.visible=true;
s.m.position.set(p.x+(Math.random()-0.5)*1.4,p.y+1.2,p.z+(Math.random()-0.5)*1.4);
s.v.set((Math.random()-0.5)*0.8,1.6+Math.random()*1.4,(Math.random()-0.5)*0.8);
s.max=s.life=1.3+Math.random()*0.6;
s.m.scale.setScalar(0.8);
return;
}
}
function updateParticles(dt){
var i,s;
for(i=0;i<sparks.length;i++){
s=sparks[i];
if(s.life<=0)continue;
s.life-=dt;
if(s.life<=0){s.m.visible=false;continue;}
s.m.position.addScaledVector(s.v,dt);
s.v.y-=28*dt;
}
for(i=0;i<smokes.length;i++){
s=smokes[i];
if(s.life<=0)continue;
s.life-=dt;
if(s.life<=0){s.m.visible=false;s.m.material.opacity=0;continue;}
var t=1-s.life/s.max;
s.m.material.opacity=0.5*(1-t);
s.m.scale.setScalar(0.8+t*2.2);
s.m.position.addScaledVector(s.v,dt);
}
}
/* ============ input ============ */
var keys={};
window.addEventListener('keydown',function(e){
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Space'].indexOf(e.code)>=0)e.preventDefault();
initAudio();
if(e.code==='KeyP'&&!e.repeat){togglePhoto();return;}
if(e.code==='KeyR'&&!e.repeat&&state==='over'){resetGame();return;}
keys[e.code]=true;
});
window.addEventListener('keyup',function(e){keys[e.code]=false;});
window.addEventListener('blur',function(){keys={};});
window.addEventListener('pointerdown',function(){initAudio();});
function playerInput(){
var thr=0,steer=0;
if(keys.KeyW||keys.ArrowUp)thr+=1;
if(keys.KeyS||keys.ArrowDown)thr-=1;
if(keys.KeyA||keys.ArrowLeft)steer+=1;
if(keys.KeyD||keys.ArrowRight)steer-=1;
return {thr:thr,steer:steer};
}
/* ============ audio (Web Audio only, no assets) ============ */
var AC=null,engineOsc=null,engineGain=null,crowdGain=null,noiseBuf=null,audioMaster=null;
function initAudio(){
if(AC){ if(AC.state==='suspended'){try{AC.resume();}catch(e){}} return; }
try{
AC=new (window.AudioContext||window.webkitAudioContext)();
audioMaster=AC.createGain(); audioMaster.gain.value=0.6; audioMaster.connect(AC.destination);
engineOsc=AC.createOscillator(); engineOsc.type='sawtooth'; engineOsc.frequency.value=65;
var lp=AC.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=700;
engineGain=AC.createGain(); engineGain.gain.value=0;
engineOsc.connect(lp); lp.connect(engineGain); engineGain.connect(audioMaster);
engineOsc.start();
noiseBuf=AC.createBuffer(1,AC.sampleRate*2,AC.sampleRate);
var d=noiseBuf.getChannelData(0);
for(var i=0;i<d.length;i++)d[i]=(Math.random()*2-1)*0.5;
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;
crowdGain=AC.createGain(); crowdGain.gain.value=0.05;
src.connect(bp); bp.connect(crowdGain); crowdGain.connect(audioMaster); src.start();
}catch(e){AC=null;}
}
function crashSound(v){
if(!AC)return;
try{
var s=AC.createBufferSource(); s.buffer=noiseBuf;
var g=AC.createGain(); var f=AC.createBiquadFilter();
f.type='lowpass'; f.frequency.value=500+v*700;
s.connect(f); f.connect(g); g.connect(audioMaster);
var t=AC.currentTime;
g.gain.setValueAtTime(Math.min(0.5,0.12+v*0.3),t);
g.gain.exponentialRampToValueAtTime(0.001,t+0.3);
s.start(t); s.stop(t+0.35);
}catch(e){}
}
function cheer(){
if(!AC||!crowdGain)return;
try{
var t=AC.currentTime;
crowdGain.gain.cancelScheduledValues(t);
crowdGain.gain.setValueAtTime(crowdGain.gain.value,t);
crowdGain.gain.linearRampToValueAtTime(0.17,t+0.15);
crowdGain.gain.exponentialRampToValueAtTime(0.05,t+2.5);
}catch(e){}
}
function updateAudio(){
if(!AC)return;
try{
var sp=player.vel.length();
engineOsc.frequency.setTargetAtTime(60+sp*7.5,AC.currentTime,0.06);
var on=(state==='play'&&!player.wrecked&&!photo);
engineGain.gain.setTargetAtTime(on?0.045+Math.min(0.035,sp*0.0015):0,AC.currentTime,0.12);
}catch(e){}
}
/* ============ damage / wreck / score ============ */
var elBanners=document.getElementById('banners');
function showBanner(text,cls){
var b=document.createElement('div');
b.className='banner'+(cls?' '+cls:'');
b.textContent=text;
elBanners.appendChild(b);
setTimeout(function(){b.remove();},1700);
}
function addScore(n){score+=n;}
function applyDamage(t,dmg,by){
if(t.wrecked||t.invulnT>0)return;
t.hp-=dmg; t.lastHitBy=by;
if(t.hp<=0)wreck(t,by);
}
function wreck(t,by){
t.wrecked=true; t.hp=0; t.vel.set(0,0,0);
t.bodyMat.color.set(0x23262b);
t.tilt.rotation.z+=(Math.random()-0.5)*0.3;
spawnSparks({x:t.pos.x,y:t.pos.y+1,z:t.pos.z},18);
crashSound(1); cheer();
if(t.isPlayer){
showBanner('WRECKED!','bad');
t.respawnT=2.5;
}else{
t.smokeT=9;
if(by&&by.isPlayer){
takedowns++;
addScore(1500);
showBanner('TAKEDOWN — '+t.name+' +1500','good');
}
}
}
function respawnPlayer(t){
t.wrecked=false; t.hp=100; t.invulnT=2;
t.bodyMat.color.set(0xcdd5dc);
t.tilt.rotation.set(0,0,0);
t.heading=t.spawn.h;
t.pos.set(t.spawn.x,groundH(t.spawn.x,t.spawn.z),t.spawn.z);
t.vel.set(0,0,0);
}
/* ============ physics ============ */
var _fwd=new THREE.Vector3(), _rgt=new THREE.Vector3();
function updateTruck(t,dt,inp){
t.invulnT=Math.max(0,t.invulnT-dt);
if(t.wrecked){
if(t.isPlayer){ t.respawnT-=dt; if(t.respawnT<=0)respawnPlayer(t); }
else if(t.smokeT>0){
t.smokeT-=dt; t.smokeAcc+=dt;
if(t.smokeAcc>=0.13){t.smokeAcc=0;spawnSmoke(t.pos);}
}
t.vel.multiplyScalar(Math.max(0,1-3*dt));
}else{
_fwd.set(Math.sin(t.heading),0,Math.cos(t.heading));
var fwdSpeed=t.vel.dot(_fwd);
var steerEff=inp.steer*2.5*Math.min(1,Math.abs(fwdSpeed)/7)*(fwdSpeed>=0?1:-1);
t.heading+=steerEff*dt;
_fwd.set(Math.sin(t.heading),0,Math.cos(t.heading));
t.vel.addScaledVector(_fwd, inp.thr*(inp.thr>0?26:14)*dt);
_rgt.set(-_fwd.z,0,_fwd.x);
var vF=t.vel.dot(_fwd), vR=t.vel.dot(_rgt);
vR*=Math.exp(-6.5*dt);
vF*=Math.exp(-0.6*dt);
if(vF>t.maxS)vF=t.maxS; if(vF<-9)vF=-9;
t.vel.copy(_fwd).multiplyScalar(vF).addScaledVector(_rgt,vR);
t.pos.addScaledVector(t.vel,dt);
for(var wi=0;wi<t.wheels.length;wi++)t.wheels[wi].rotation.z-=vF*dt/0.78;
/* wall */
var r=Math.hypot(t.pos.x,t.pos.z);
if(r>57){
var nx=t.pos.x/r, nz=t.pos.z/r;
t.pos.x=nx*57; t.pos.z=nz*57;
var vn=t.vel.x*nx+t.vel.z*nz;
if(vn>0){
t.vel.x-=nx*vn*1.5; t.vel.z-=nz*vn*1.5;
if(vn>9){
applyDamage(t,(vn-9)*1.2,null);
spawnSparks({x:t.pos.x,y:t.pos.y+1,z:t.pos.z},6);
crashSound(Math.min(1,vn/16));
if(t.isPlayer)shake=Math.min(1.2,shake+vn*0.04);
}
}
}
/* obstacles */
for(var oi=0;oi<obstacles.length;oi++){
var o=obstacles[oi];
var dx=t.pos.x-o.x, dz=t.pos.z-o.z;
var d=Math.hypot(dx,dz), minD=o.r+2.1;
if(d<minD&&d>0.01){
var onx=dx/d, onz=dz/d;
t.pos.x=o.x+onx*minD; t.pos.z=o.z+onz*minD;
var ovn=t.vel.x*onx+t.vel.z*onz;
if(ovn<0){
t.vel.x-=onx*ovn*1.45; t.vel.z-=onz*ovn*1.45;
var imp=-ovn;
if(imp>6){
applyDamage(t,(imp-6)*1.0,null);
spawnSparks({x:t.pos.x,y:t.pos.y+0.8,z:t.pos.z},5);
crashSound(Math.min(0.7,imp/18));
if(t.isPlayer)shake=Math.min(1.2,shake+imp*0.03);
}
}
}
}
}
/* placement on heightfield */
var rr=Math.hypot(t.pos.x,t.pos.z);
if(rr>57){t.pos.x*=57/rr;t.pos.z*=57/rr;}
t.pos.y+=(groundH(t.pos.x,t.pos.z)-t.pos.y)*Math.min(1,dt*10);
t.group.rotation.y=t.heading-Math.PI/2;
if(!t.wrecked){
_fwd.set(Math.sin(t.heading),0,Math.cos(t.heading));
_rgt.set(-_fwd.z,0,_fwd.x);
var pF=groundH(t.pos.x+_fwd.x*1.8,t.pos.z+_fwd.z*1.8);
var pB=groundH(t.pos.x-_fwd.x*1.8,t.pos.z-_fwd.z*1.8);
var pitch=Math.atan2(pF-pB,3.6);
var hR=groundH(t.pos.x+_rgt.x*1.3,t.pos.z+_rgt.z*1.3);
var hL=groundH(t.pos.x-_rgt.x*1.3,t.pos.z-_rgt.z*1.3);
var roll=Math.atan2(hL-hR,2.6);
var k=Math.min(1,dt*8);
t.tilt.rotation.z+=(pitch-t.tilt.rotation.z)*k;
t.tilt.rotation.x+=(roll-t.tilt.rotation.x)*k;
}
}
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];
var dx=b.pos.x-a.pos.x, dz=b.pos.z-a.pos.z;
var d=Math.hypot(dx,dz), minD=4.4;
if(d>=minD||d<1e-4)continue;
var nx=dx/d, nz=dz/d, ov=minD-d;
if(a.wrecked&&b.wrecked)continue;
if(a.wrecked){b.pos.x+=nx*ov;b.pos.z+=nz*ov;}
else if(b.wrecked){a.pos.x-=nx*ov;a.pos.z-=nz*ov;}
else{a.pos.x-=nx*ov/2;a.pos.z-=nz*ov/2;b.pos.x+=nx*ov/2;b.pos.z+=nz*ov/2;}
var rvx=b.vel.x-a.vel.x, rvz=b.vel.z-a.vel.z;
var vn=rvx*nx+rvz*nz;
if(vn>=0)continue;
var e=0.4;
if(a.wrecked){ b.vel.x-=nx*(1+e)*vn; b.vel.z-=nz*(1+e)*vn; }
else if(b.wrecked){ a.vel.x+=nx*(1+e)*vn; a.vel.z+=nz*(1+e)*vn; }
else{
var jn=-(1+e)*vn/2;
a.vel.x-=nx*jn; a.vel.z-=nz*jn;
b.vel.x+=nx*jn; b.vel.z+=nz*jn;
}
var impact=-vn;
if(impact>2.5){
spawnSparks({x:(a.pos.x+b.pos.x)/2,y:Math.max(a.pos.y,b.pos.y)+1,z:(a.pos.z+b.pos.z)/2},
Math.min(16,4+Math.floor(impact)));
crashSound(Math.min(1,impact/16));
shake=Math.min(1.2,shake+impact*0.045);
if(!a.wrecked)applyDamage(a,impact*1.15*(a.isPlayer?0.8:1),b);
if(!b.wrecked)applyDamage(b,impact*1.15*(b.isPlayer?0.8:1),a);
if(a.isPlayer||b.isPlayer){
var pts=Math.round(impact*7);
addScore(pts);
if(impact>10)showBanner('SLAM +'+pts,'good');
}
}
}
}
/* ============ AI: sector roaming + separation + cooldown charges ============ */
function aiDrive(t,dt){
t.aggroCd-=dt; t.chargeT-=dt; t.aiTimer-=dt;
if(t.chargeT<=0&&t.aggroCd<=0&&Math.random()<dt*0.4){
var best=null,bd=26;
for(var i=0;i<trucks.length;i++){
var o=trucks[i];
if(o===t||o.wrecked)continue;
var dd=o.pos.distanceTo(t.pos);
if(dd<bd){bd=dd;best=o;}
}
if(best){t.chargeT=3.0;t.chargeTarget=best;t.aggroCd=6+Math.random()*7;}
}
var tx,tz;
if(t.chargeT>0&&t.chargeTarget&&!t.chargeTarget.wrecked){
tx=t.chargeTarget.pos.x; tz=t.chargeTarget.pos.z;
}else{
t.chargeTarget=null;
if(!t.target||t.aiTimer<=0||Math.hypot(t.pos.x-t.target.x,t.pos.z-t.target.z)<5){
var a=t.homeAngle+(Math.random()-0.5)*1.7;
var r=14+Math.random()*32;
t.target={x:Math.sin(a)*r,z:Math.cos(a)*r};
t.aiTimer=4+Math.random()*5;
}
tx=t.target.x; tz=t.target.z;
}
var dx=tx-t.pos.x, dz=tz-t.pos.z;
var dl=Math.hypot(dx,dz)||1; dx/=dl; dz/=dl;
var ax=0,az=0;
for(i=0;i<trucks.length;i++){
var q=trucks[i];
if(q===t)continue;
var sx=t.pos.x-q.pos.x, sz=t.pos.z-q.pos.z, sd=Math.hypot(sx,sz);
if(sd<8&&sd>0.01){ax+=sx/sd*(8-sd)/8*1.4;az+=sz/sd*(8-sd)/8*1.4;}
}
var fx=Math.sin(t.heading), fz=Math.cos(t.heading);
for(i=0;i<obstacles.length;i++){
var ob=obstacles[i];
var px=t.pos.x+fx*6-ob.x, pz=t.pos.z+fz*6-ob.z;
if(Math.hypot(px,pz)<ob.r+4){
var ox=t.pos.x-ob.x, oz=t.pos.z-ob.z, od=Math.hypot(ox,oz)||1;
ax+=ox/od*1.6; az+=oz/od*1.6;
}
}
var rr=Math.hypot(t.pos.x,t.pos.z);
if(rr>46){ax-=t.pos.x/rr*1.2;az-=t.pos.z/rr*1.2;}
var dirX=dx+ax, dirZ=dz+az;
var L=Math.hypot(dirX,dirZ)||1; dirX/=L; dirZ/=L;
var desired=Math.atan2(dirX,dirZ);
var diff=desired-t.heading;
while(diff>Math.PI)diff-=2*Math.PI;
while(diff<-Math.PI)diff+=2*Math.PI;
var steer=Math.max(-1,Math.min(1,diff*2.5));
var thr=Math.abs(diff)>1.9?0.35:1;
if(t.chargeT>0)thr=1;
if(t.revT>0){t.revT-=dt;return{thr:-1,steer:-steer};}
var sp=Math.hypot(t.vel.x,t.vel.z);
if(thr>0.5&&sp<1.3){t.stuckT+=dt;if(t.stuckT>1.1){t.stuckT=0;t.revT=0.85;}}
else t.stuckT=Math.max(0,t.stuckT-dt);
return {thr:thr,steer:steer};
}
/* ============ camera ============ */
function updateCamera(dt){
var t=player;
var fx=Math.sin(t.heading), fz=Math.cos(t.heading);
var cx=t.pos.x-fx*11, cz=t.pos.z-fz*11, cy=t.pos.y+6;
var k=1-Math.exp(-5*dt);
camera.position.x+=(cx-camera.position.x)*k;
camera.position.y+=(cy-camera.position.y)*k;
camera.position.z+=(cz-camera.position.z)*k;
if(shake>0){
camera.position.x+=(Math.random()-0.5)*shake*0.5;
camera.position.y+=(Math.random()-0.5)*shake*0.4;
shake=Math.max(0,shake-dt*2.5);
}
camera.lookAt(t.pos.x+fx*5, t.pos.y+1.6, t.pos.z+fz*5);
}
/* ============ HUD / modes ============ */
var elClock=document.getElementById('clock'), elScore=document.getElementById('score');
var elHull=document.getElementById('hullFill');
var photoCard=document.getElementById('photoCard'), photoScore=document.getElementById('photoScore');
var endCard=document.getElementById('endCard'), endScore=document.getElementById('endScore');
var endTk=document.getElementById('endTk');
var lastClock='', lastScore='';
function fmt(t){
t=Math.max(0,Math.ceil(t));
var m=Math.floor(t/60), s=t%60;
return m+':'+(s<10?'0':'')+s;
}
function updateHUD(){
var ts=fmt(timeLeft);
if(ts!==lastClock){elClock.textContent=ts;lastClock=ts;}
var ss=String(score);
if(ss!==lastScore){elScore.textContent=ss;lastScore=ss;}
elHull.style.width=Math.max(0,player.hp)+'%';
}
function togglePhoto(){
photo=!photo;
photoCard.style.display=photo?'flex':'none';
if(photo)photoScore.textContent=score;
}
function endGame(){
state='over';
endScore.textContent=score;
endTk.textContent='TAKEDOWNS: '+takedowns+' · '+ARENA;
endCard.style.display='flex';
}
function resetGame(){
score = DEMO?5000:0;
timeLeft = DEMO?20:90;
takedowns=0; state='play';
trucks.forEach(function(t){
t.wrecked=false; t.hp=100; t.invulnT=0;
t.bodyMat.color.set(0xcdd5dc);
t.tilt.rotation.set(0,0,0);
t.vel.set(0,0,0);
t.heading=t.spawn.h;
t.pos.set(t.spawn.x,groundH(t.spawn.x,t.spawn.z),t.spawn.z);
t.smokeT=0; t.target=null; t.chargeT=0; t.chargeTarget=null;
});
sparks.forEach(function(s){s.life=0;s.m.visible=false;});
smokes.forEach(function(s){s.life=0;s.m.visible=false;s.m.material.opacity=0;});
endCard.style.display='none';
showBanner('CYBERSLAM — GO!');
}
/* ============ main loop ============ */
resetGame();
camera.position.set(player.pos.x, player.pos.y+6, player.pos.z+11);
camera.lookAt(player.pos);
var last=performance.now();
function loop(now){
requestAnimationFrame(loop);
var dt=Math.min(0.033,(now-last)/1000)||0.016;
last=now;
if(photo)return; /* photo mode: physics step AND render are both skipped */
if(state==='play'){
simTime+=dt;
timeLeft-=dt;
if(timeLeft<=0){timeLeft=0;endGame();}
updateTruck(player,dt,playerInput());
for(var i=0;i<trucks.length;i++){
var t=trucks[i];
if(t.isPlayer)continue;
if(t.wrecked)updateTruck(t,dt,{thr:0,steer:0});
else updateTruck(t,dt,aiDrive(t,dt));
}
collideTrucks();
}else{
simTime+=dt*0.5;
}
updateParticles(dt);
updateCrowd();
for(var bi=0;bi<barrelMats.length;bi++){
barrelMats[bi].emissiveIntensity=0.75+0.35*Math.sin(simTime*13+bi*2)+Math.random()*0.15;
}
updateCamera(dt);
updateHUD();
updateAudio();
renderer.render(scene,camera);
}
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
#demo routes through the same resetGame() everyone else uses and sets the real state variables — score = 5000; timeLeft = 20; — which the countdown, the HUD, the end-of-match trigger, and the restart path all read, so the demo simply is a match that started 70 seconds in and 5,000 points up; there is no cosmetic layer anywhere.P, the rAF callback returns before the physics update and before renderer.render, so the last frame stays painted on the canvas while clock, AI, particles, crowd wave, and audio ducking are all frozen; releasing P resumes from the exact same state rather than a simulation that ran on ahead.