Rally
GLM 5.3 Flash · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 18,090 bytes · SHA-256 9290bc2c89e0
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>Dusty Ridge Rally</title>
<style>
:root{ --hud:#fff; --accent:#ffb347; }
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{width:100%;height:100%;overflow:hidden;background:#0a0d14;
font-family:"Segoe UI",system-ui,sans-serif;color:var(--hud)}
#wrap{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
/* Canvas keeps a 16:9 aspect box centered in the window */
#stage{position:relative;width:100%;height:100%;max-width:100vw;max-height:100vh}
canvas#game{display:block;width:100%;height:100%}
/* ---------- HUD ---------- */
.hud{position:absolute;pointer-events:none;text-shadow:0 2px 4px rgba(0,0,0,.7)}
#speedo{right:2.5%;bottom:5%;text-align:right}
#speedVal{font-size:min(9vh,64px);font-weight:800;line-height:.95;font-variant-numeric:tabular-nums}
#speedUnit{font-size:min(2.6vh,16px);letter-spacing:3px;opacity:.8}
#gearBox{font-size:min(3.4vh,22px);color:var(--accent);font-weight:700}
#rpmBar{width:26vmin;max-width:220px;height:8px;background:rgba(255,255,255,.15);
border-radius:4px;margin-top:6px;overflow:hidden}
#rpmFill{height:100%;width:0;background:linear-gradient(90deg,#7CFC00,#ffd700,#ff4040);
border-radius:4px;transition:none}
#topLeft{left:2.5%;top:3%;font-size:min(3vh,20px);line-height:1.5;font-variant-numeric:tabular-nums}
#lapLine b,#timeLine b{color:var(--accent)}
#bestLap{opacity:.85;font-size:.8em}
#minimapWrap{position:absolute;left:2.5%;bottom:4%;width:24vmin;height:24vmin;
max-width:190px;max-height:190px;background:rgba(10,14,22,.45);
border:1px solid rgba(255,255,255,.25);border-radius:10px;pointer-events:none}
#minimap{width:100%;height:100%}
#centerMsg{left:50%;top:32%;transform:translate(-50%,-50%);text-align:center;
font-size:min(18vh,140px);font-weight:900;color:var(--accent);
text-shadow:0 0 30px rgba(255,179,71,.6),0 4px 8px rgba(0,0,0,.8)}
#subMsg{left:50%;top:46%;transform:translateX(-50%);font-size:min(3.4vh,22px);
letter-spacing:2px;opacity:0}
#offTrackWarn{left:50%;top:62%;transform:translateX(-50%);font-size:min(2.8vh,18px);
color:#ff6b6b;letter-spacing:3px;opacity:0;transition:opacity .2s}
/* ---------- Menus ---------- */
.overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
justify-content:center;background:rgba(6,9,16,.72);backdrop-filter:blur(4px);z-index:20}
.overlay h1{font-size:min(9vh,58px);letter-spacing:4px;margin-bottom:.4em;
color:var(--accent);text-shadow:0 4px 16px rgba(0,0,0,.8)}
.overlay h2{font-size:min(5vh,30px);margin-bottom:.6em}
.overlay p{margin:.25em 0;font-size:min(3vh,19px);opacity:.9}
.btn{pointer-events:auto;cursor:pointer;margin:8px;padding:12px 38px;font-size:min(3.4vh,21px);
font-weight:700;letter-spacing:2px;color:#111;background:var(--accent);border:none;
border-radius:8px;box-shadow:0 4px 0 #b36f1e;transition:transform .08s}
.btn:active{transform:translateY(3px);box-shadow:0 1px 0 #b36f1e}
.btn.alt{background:#cfd8e3;box-shadow:0 4px 0 #7c8698}
.hidden{display:none!important}
#controlsHelp{position:absolute;right:2.5%;top:3%;text-align:right;font-size:min(2.2vh,13px);
opacity:.65;line-height:1.55;pointer-events:none;text-shadow:0 1px 3px #000}
/* ---------- Touch controls ---------- */
#touchUI{position:absolute;inset:0;pointer-events:none;z-index:15;display:none}
body.touch #touchUI{display:block}
.tbtn{position:absolute;pointer-events:auto;width:15vmin;height:15vmin;max-width:84px;max-height:84px;
border-radius:50%;background:rgba(255,255,255,.14);border:2px solid rgba(255,255,255,.35);
display:flex;align-items:center;justify-content:center;font-size:min(5.5vmin,30px);
font-weight:800;user-select:none;touch-action:none}
.tbtn.on{background:rgba(255,179,71,.45)}
#tLeft{left:4%;bottom:6%} #tRight{left:calc(4% + 17vmin);bottom:6%}
#tGas{right:4%;bottom:6%} #tBrake{right:calc(4% + 17vmin);bottom:6%}
#tHand{right:4%;bottom:calc(6% + 17vmin);font-size:min(4vmin,20px)}
</style>
</head>
<body>
<div id="wrap"><div id="stage">
<canvas id="game"></canvas>
<div class="hud" id="topLeft">
<div id="lapLine">LAP <b id="lapNum">1</b> / <span id="totalLaps">3</span></div>
<div id="timeLine">TIME <b id="curTime">0:00.00</b></div>
<div id="bestLap">BEST —</div>
<div id="lastLap">LAST —</div>
</div>
<div class="hud" id="speedo">
<div id="gearBox">N</div>
<div id="speedVal">0</div>
<div id="speedUnit">KM/H</div>
<div id="rpmBar"><div id="rpmFill"></div></div>
</div>
<div id="controlsHelp">WASD / Arrows drive · Space handbrake<br>C camera · P pause · R restart</div>
<div class="hud" id="centerMsg"></div>
<div class="hud" id="subMsg"></div>
<div class="hud" id="offTrackWarn">OFF TRACK!</div>
<div id="minimapWrap"><canvas id="minimap" width="200" height="200"></canvas></div>
<div id="touchUI">
<div class="tbtn" id="tLeft">◀</div><div class="tbtn" id="tRight">▶</div>
<div class="tbtn" id="tBrake">▼</div><div class="tbtn" id="tGas">▲</div>
<div class="tbtn" id="tHand">DRIFT</div>
</div>
<!-- Start screen -->
<div class="overlay" id="startScreen">
<h1>DUSTY RIDGE RALLY</h1>
<p>3 laps · hills, dirt & mud · beat your best time</p>
<p style="opacity:.7;font-size:.85em">Stay on the asphalt — grass is slow, mud is worse.</p>
<button class="btn" id="startBtn">START RACE</button>
</div>
<!-- Pause menu -->
<div class="overlay hidden" id="pauseScreen">
<h2>PAUSED</h2>
<button class="btn" id="resumeBtn">RESUME</button>
<button class="btn alt" id="restartBtn1">RESTART</button>
</div>
<!-- Finish screen -->
<div class="overlay hidden" id="finishScreen">
<h1>FINISH!</h1>
<p>Total time: <b id="finalTime">—</b></p>
<p>Best lap: <b id="finalBest">—</b></p>
<p id="finalNote" style="color:var(--accent)"></p>
<button class="btn" id="restartBtn2">RACE AGAIN</button>
</div>
</div></div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
'use strict';
/* =========================================================================
DUSTY RIDGE RALLY — single-file Three.js rally racer
Modules: Audio (WebAudio synth), Input, Track, Scenery, Car (physics+mesh),
FX (particles/skids), CameraRig, HUD/UI, Game (state machine)
========================================================================= */
const Game = (() => {
/* ---------------- Config ---------------- */
const CFG = {
laps: 3,
trackWidth: 11, // drivable half-width = width/2
samples: 420, // spline resolution for road mesh & lookups
fogColor: 0xcfe3ef,
fogNear: 90, fogFar: 520,
gravity: 22,
};
/* Surface multipliers: grip, topSpeed */
const SURF = {
road: {grip: 1.00, top: 1.00},
grass:{grip: 0.42, top: 0.52},
gravel:{grip:0.55, top: 0.66},
mud: {grip: 0.28, top: 0.35},
};
/* ---------------- Utilities ---------------- */
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
const lerp=(a,b,t)=>a+(b-a)*t;
const rand=(a,b)=>a+Math.random()*(b-a);
const TAU=Math.PI*2;
/* ================= AUDIO (all synthesized) ================= */
const Audio = (() => {
let ctx=null, master=null, engOsc=null, engOsc2=null, engGain=null,
skidSrc=null, skidGain=null, skidFilter=null, started=false;
function ensure(){
if(ctx) return;
ctx = new (window.AudioContext||window.webkitAudioContext)();
master = ctx.createGain(); master.gain.value=.55; master.connect(ctx.destination);
// engine: two detuned saws through lowpass
engGain = ctx.createGain(); engGain.gain.value=0;
const lp = ctx.createBiquadFilter(); lp.type='lowpass'; lp.frequency.value=900;
engOsc = ctx.createOscillator(); engOsc.type='sawtooth'; engOsc.frequency.value=60;
engOsc2 = ctx.createOscillator(); engOsc2.type='square'; engOsc2.frequency.value=31;
const g2 = ctx.createGain(); g2.gain.value=.4;
engOsc.connect(lp); engOsc2.connect(g2); g2.connect(lp); lp.connect(engGain); engGain.connect(master);
engOsc.start(); engOsc2.start();
// skid/gravel: looping filtered noise
const len = ctx.sampleRate*1.5, buf = ctx.createBuffer(1,len,ctx.sampleRate),
d = buf.getChannelData(0);
for(let i=0;i<len;i++) d[i]=Math.random()*2-1;
skidSrc = ctx.createBufferSource(); skidSrc.buffer=buf; skidSrc.loop=true;
skidFilter = ctx.createBiquadFilter(); skidFilter.type='bandpass';
skidFilter.frequency.value=800; skidFilter.Q.value=.6;
skidGain = ctx.createGain(); skidGain.gain.value=0;
skidSrc.connect(skidFilter); skidFilter.connect(skidGain); skidGain.connect(master);
skidSrc.start();
started=true;
}
function resume(){ ensure(); if(ctx.state==='suspended') ctx.resume(); }
// engine update: rpm 0..1, throttle 0..1
function engine(rpm, load, vol){
if(!started) return;
const f = 48 + rpm*230;
engOsc.frequency.setTargetAtTime(f, ctx.currentTime, .03);
engOsc2.frequency.setTargetAtTime(f*.502, ctx.currentTime, .03);
engGain.gain.setTargetAtTime(vol*(0.05+load*0.10+rpm*0.06), ctx.currentTime, .05);
}
function skid(amount, freq){
if(!started) return;
skidGain.gain.setTargetAtTime(clamp(amount,0,1)*.16, ctx.currentTime,.05);
skidFilter.frequency.setTargetAtTime(freq||700, ctx.currentTime,.1);
}
function beep(freq,dur,type){
if(!started) return;
const o=ctx.createOscillator(), g=ctx.createGain();
o.type=type||'square'; o.frequency.value=freq;
g.gain.setValueAtTime(.25,ctx.currentTime);
g.gain.exponentialRampToValueAtTime(.001,ctx.currentTime+dur);
o.connect(g); g.connect(master); o.start(); o.stop(ctx.currentTime+dur);
}
function crash(mag){
if(!started) return;
const dur=.25, n=ctx.createBufferSource(),
buf=ctx.createBuffer(1,ctx.sampleRate*dur,ctx.sampleRate),
d=buf.getChannelData(0);
for(let i=0;i<d.length;i++) d[i]=(Math.random()*2-1)*(1-i/d.length);
n.buffer=buf;
const f=ctx.createBiquadFilter(); f.type='lowpass'; f.frequency.value=300+mag*500;
const g=ctx.createGain(); g.gain.value=clamp(mag,.1,1)*.6;
n.connect(f); f.connect(g); g.connect(master); n.start();
}
function finishTone(){ if(!started)return;
[523,659,784,1047].forEach((f,i)=>setTimeout(()=>beep(f,.35,'triangle'),i*160));
}
return {resume, engine, skid, beep, crash, finishTone};
})();
/* ================= INPUT ================= */
const Input = (() => {
const keys={}, touch={left:0,right:0,gas:0,brake:0,hand:0};
let camToggleCb=null;
addEventListener('keydown',e=>{
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight','Space'].includes(e.code)) e.preventDefault();
if(e.repeat) return;
keys[e.code]=true;
if(e.code==='KeyC'&&camToggleCb) camToggleCb();
if(e.code==='KeyP') Game.togglePause();
if(e.code==='KeyR') Game.restart();
});
addEventListener('keyup',e=>keys[e.code]=false);
// touch buttons
function bind(id,name){
const el=document.getElementById(id);
const on=e=>{e.preventDefault();touch[name]=1;el.classList.add('on');};
const off=e=>{e.preventDefault();touch[name]=0;el.classList.remove('on');};
el.addEventListener('touchstart',on,{passive:false});
el.addEventListener('touchend',off);el.addEventListener('touchcancel',off);
el.addEventListener('mousedown',on);el.addEventListener('mouseup',off);
el.addEventListener('mouseleave',()=>{touch[name]=0;el.classList.remove('on');});
}
['tLeft:left','tRight:right','tGas:gas','tBrake:brake','tHand:hand']
.forEach(s=>{const[a,b]=s.split(':');bind(a,b);});
return {
get throttle(){return (keys.ArrowUp||keys.KeyW||touch.gas)?1:0;},
get brake(){return (keys.ArrowDown||keys.KeyS||touch.brake)?1:0;},
get steer(){return ((keys.ArrowLeft||keys.KeyA||touch.left)?1:0)-((keys.ArrowRight||keys.KeyD||touch.right)?1:0);},
get hand(){return (keys.Space||touch.hand)?1:0;},
setCamCb(cb){camToggleCb=cb;}
};
})();
/* ================= TRACK =================
Closed CatmullRom loop through control points with elevation.
Builds road ribbon, shoulders, center dashes, checkpoint gates,
and provides surface lookup (road/grass/mud) for physics. */
const Track = (() => {
const pts = [
[0,0],[60,-6],[120,4],[170,30],[195,80],[185,135],
[140,165],[85,158],[40,180],[10,225],[-40,240],[-95,220],
[-125,175],[-115,120],[-150,75],[-185,30],[-160,-25],[-105,-45],
[-50,-30]
].map(p=>new THREE.Vector3(p[0], 0, p[1]));
// elevation profile: gentle hills & dips added procedurally
pts.forEach((p,i)=>{
p.y = Math.sin(i/pts.length*TAU*2)*4 + Math.sin(i/pts.length*TAU*5)*1.8;
});
const curve = new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.5);
const HW = CFG.trackWidth/2;
// sampled frames
const N=CFG.samples;
const centers=[], tangents=[], normals=[]; // normals = left vectors (x,z only)
for(let i=0;i<N;i++){
const t=i/N, c=curve.getPointAt(t), tg=curve.getTangentAt(t);
tg.y=0; tg.normalize();
centers.push(c); tangents.push(tg);
normals.push(new THREE.Vector3(-tg.z,0,tg.x));
}
const centerYs = centers.map(c=>c.y);
// mud pits: parameter positions + radius across the track
const mudPits=[{t:.14,r:9},{t:.44,r:8},{t:.78,r:10}];
const waterPit={t:.93,r:7};
/* --- geometry builders --- */
function ribbon(halfInner,halfOuter,colorFn,yOff){
// builds a closed ribbon between two lateral offsets from centerline
const g=new THREE.BufferGeometry(), pos=[], col=[], idx=[];
const cA=new THREE.Color();
for(let i=0;i<=N;i++){
const k=i%N, c=centers[k], n=normals[k];
pos.push(c.x-n.x*halfOuter, c.y+yOff, c.z-n.z*halfOuter);
pos.push(c.x+n.x*halfOuter, c.y+yOff, c.z+n.z*halfOuter);
const c1=colorFn(k/N,0), c2=colorFn(k/N,1);
cA.copy(c1); col.push(cA.r,cA.g,cA.b);
cA.copy(c2); col.push(cA.r,cA.g,cA.b);
if(i<N){const a=i*2; idx.push(a,a+1,a+2, a+1,a+3,a+2);}
}
g.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));
g.setAttribute('color',new THREE.Float32BufferAttribute(col,3));
g.setIndex(idx); g.computeVertexNormals();
return new THREE.Mesh(g,new THREE.MeshLambertMaterial({vertexColors:true}));
}
function build(scene){
const grp=new THREE.Group();
// ground plane (big, follows nothing — static under whole map)
const gg=new THREE.CircleGeometry(700,48);
const gm=new THREE.MeshLambertMaterial({color:0x6f9e4f});
const ground=new THREE.Mesh(gg,gm); ground.rotation.x=-Math.PI/2;
ground.position.y=-0.35; ground.receiveShadow=true; grp.add(ground);
// grass shoulder (slightly wider than road, darker green->edge blend)
grp.add(ribbon(HW+.2, HW+4.5,(u)=>new THREE.Color(0x74924f).lerp(new THREE.Color(0x6f9e4f),Math.random()*.3),0.02));
// gravel verge
grp.add(ribbon(HW-.1, HW+.35,(u)=>new THREE.Color().setHSL(.09,.28,.42+Math.random()*.05),0.04));
// asphalt road with subtle variation + edge tinting
grp.add(ribbon(0,HW,(u,side)=>{
const base=new THREE.Color(0x4a4a4e).offsetHSL(0,0,(Math.random()-.5)*.035);
if(side===1&&u>0) {} // keep simple
return base;
},0.06));
// center dashes: thin bright quads every few samples
const dashGeo=new THREE.PlaneGeometry(0.35,2.2); dashGeo.rotateX(-Math.PI/2);
const dashMat=new THREE.MeshBasicMaterial({color:0xf2ead8});
const dashes=new THREE.InstancedMesh(dashGeo,dashMat,N/6|0);
const m4=new THREE.Matrix4(), q=new THREE.Quaternion(), up=new THREE.Vector3(0,1,0);
let di=0;
for(let i=0;i<N;i+=6){
const c=centers[i],tg=tangents[i];
q.setFromAxisAngle(up,Math.atan2(tg.x,tg.z));
m4.compose(new THREE.Vector3(c.x,c.y+.09,c.z),q,new THREE.Vector3(1,1,1));
dashes.setMatrixAt(di++,m4);
}
dashes.count=di; grp.add(dashes);
// mud pits (dark brown discs) + one shallow "water" pit (blue-brown)
const discGeo=new THREE.CircleGeometry(1,20); discGeo.rotateX(-Math.PI/2);
mudPits.forEach(mp=>{
const c=curve.getPointAt(mp.t);
const m=new THREE.Mesh(discGeo,new THREE.MeshLambertMaterial({color:0x513a22}));
m.scale.set(mp.r,1,mp.r*.8); m.position.set(c.x,c.y+.08,c.z); grp.add(m);
});
const wc=curve.getPointAt(waterPit.t);
const wm=new THREE.Mesh(discGeo,new THREE.MeshPhongMaterial({color:0x4a6a8a,shininess:90}));
wm.scale.set(waterPit.r,1,waterPit.r*.85); wm.position.set(wc.x,wc.y+.07,wc.z); grp.add(wm);
scene.add(grp);
}
/* --- spatial lookup: nearest sample index for a position --- */
function nearest(x,z,hint){
// search around hint index for speed; fall back to coarse scan
let best=-1,bd=Infinity;
if(hint>=0){
for(let o=-14;o<=14;o++){
const k=((hint+o)%N+N)%N, dx=x-centers[k].x, dz=z-centers[k].z,
d=dx*dx+dz*dz;
if(d<bd){bd=d;best=k;}
}
if(bd<40*40) return {k:best,dist:Math.sqrt(bd)};
}
for(let k=0;k<N;k+=4){
const dx=x-centers[k].x,dz=z-centers[k].z,d=dx*dx+dz*dz;
if(d<bd){bd=d;best=k;}
}
return {k:best,dist:Math.sqrt(bd)};
}
/* surface type at world pos: returns {type, lat} lat=lateral offset signed */
function surfaceAt(x,z,hint){
const {k,dist}=nearest(x,z,hint);
const c=centers[k],n=normals[k];
const lat=(x-c.x)*n.x+(z-c.z)*n.z;
let type='grass';
if(dist<HW) type='road';
else if(dist<HW+2.2) type='gravel';
// mud?
for(const mp of mudPits.concat([waterPit])){
const mc=curve.getPointAt(mp.t);
const dx=x-mc.x,dz=z-mc.z;
if(dx*dx/(mp.r*mp.r)+dz*dz/(mp.r*mp.r*.8)<1){type=mp===waterPit?'mud':'mud';break;}
}
return {type,lat,k};
}
function pointAt(t){return curve.getPointAt(((t%1)+1)%1);}
return {build,surfaceAt,pointAt,curve,centers,normals,N,HW,mudPits,waterPit};
})();